Jump to content

[1.12] Getting the correct player movement speed in blocks per second


Recommended Posts

Posted (edited)

So I have a pretty basic method to get a player's movement speed:

Spoiler

	public static double getSpeed(EntityPlayer e) {
		Vec3d lastPos = new Vec3d(e.lastTickPosX, e.lastTickPosY, e.lastTickPosZ);
		Vec3d pos = new Vec3d(e.posX, e.posY, e.posZ);

		return Math.abs(lastPos.distanceTo(pos) * 20d);
	}

 

However, when testing it, I ran into a problem

On singleplayer, walking speed was about 4.3 blocks per second (accurate according to minecraft wiki), but on a multiplayer server, it was at 5.2 bps.

Correct me if I'm wrong, but I think this difference is because of the server's tickrate being higher than 20 tps, which is what I'm multiplying the distance that the player moved in 1 tick by to get the distance moved in 1 second.

 

I've tried things like

e.getServer().getTickCounter()

and other methods that include using system time and whatnot to get the tps, but to no avail.

 

So how would I get the server's actual tps to multiply by, if that is what's wrong here?

Edited by Prickles
Posted

Was it averaging 5.2 or did you just see it occasionally. On a multiplayer server you will probably see a lot more variation due to lag. But on average it should be the same. And if the the server was running slower than 20 ticks per minute, I think the player would also move slower on average because the server would change the position each tick normally but would have less opportunities to change. On the client side though the speed can definitely jump more because the client will process movement in between server position updates. The server always decides the "truth" about the position, but in order to make movement seem smooth on the client the client will process movement as well but then jump to correct position when server packet with movement update arrives.

 

To make the calculation smooth, the best way is to calculate it on the server and send a packet once per second (or some similar frequency) to the client that contains the calculated value. Alternatively, you could do it on the client but should average it over several ticks.

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

Posted

So would I have to send a packet to the server with an EntityPlayer in it and return the speed in an Imessage in PacketHandler#onMessage? How would I access that speed then?

Posted (edited)

I tried to interpret what you said, but it didn't work. No stacktrace/crash, but it couldn't find the player/change the speed value. This was also spammed in the console:

[18:32:28] [main/WARN]: Received passengers for unknown entity

 

Main#initialize

Spoiler

	public static void initialize() {	
		snw = NetworkRegistry.INSTANCE.newSimpleChannel(Reference.MODID); 
		snw.registerMessage(SpeedPacketHandler.class, SpeedPacket.class, 0, Side.CLIENT); 
		
		fillFriendlyUUIDs();
		fillMorphColorArr();
	}

 

 

Main#preInit

Spoiler

	@EventHandler
	public static void preInit(FMLPreInitializationEvent e) {
		proxy.preInit(e);
		
		initialize();

		MinecraftForge.EVENT_BUS.register(HelmetHandler.class);
	}

 

 

HelmetHandler#following

Spoiler

	public static void following(RenderGameOverlayEvent.Pre event) {
		Minecraft mc = Minecraft.getMinecraft();
		List<EntityPlayer> players = mc.world.playerEntities;

		List<EntityPlayer> chasers = new ArrayList<EntityPlayer>();

		for (EntityPlayer e : players) {
			if (!Main.friendlyUUID.contains(e.getUniqueID())) {
				if (mc.player.getDistanceToEntity(e) <= warningDist) {
					if (chasers.size() <= maxChasers) {
						chasers.add(e);
					}
				}
			}
		}

		if (chasers.size() > 0) {
			RenderUtils.drawNearBox(event, chasers);
		}
	}

 

 

Part of RenderUtils#drawNearBox

Spoiler

	public static double speedDouble = 0d;

	public static void drawNearBox(RenderGameOverlayEvent.Pre evt, List<EntityPlayer> l) {
		Minecraft mc = Minecraft.getMinecraft();
		ScaledResolution sr = new ScaledResolution(mc);

		float scale = 0.5f;

		List<String> text = new ArrayList<String>();
		text.add("Near");

		for (int i = 0; i < l.size(); i++) {
			if (!isNPC(l.get(i))) {
				if(l.get(i) instanceof EntityPlayerMP) {
					Main.snw.sendTo(new SpeedPacket(l.get(i)), (EntityPlayerMP) l.get(i));
				}
				
				String speed = (Math.round(speedDouble * 10d) / 10d) + " bps";
				String distance = Math.round(mc.player.getDistanceToEntity(l.get(i)) * 10f) / 10f + "m";

				text.add(l.get(i).getName() + " | " + speed + " | " + distance);
			}
		}

I'm checking if the player in the EntityPlayer list is an instance of EntityPlayerMP here because it gives me this error if I don't:


java.lang.ClassCastException: net.minecraft.client.entity.EntityOtherPlayerMP cannot be cast to net.minecraft.entity.player.EntityPlayerMP
	at prickles.insight.utils.RenderUtils.drawNearBox(RenderUtils.java:40) ~[RenderUtils.class:?]

 

 

SpeedPacket.java

Spoiler

public class SpeedPacket implements IMessage {
	public SpeedPacket() {}
	
	public EntityPlayer p;
	
	public SpeedPacket(EntityPlayer p) {
		this.p = p;
	}

	@Override
	public void toBytes(ByteBuf buf) {
		ByteBufUtils.writeUTF8String(buf, p.getUniqueID().toString());
	}

	@Override
	public void fromBytes(ByteBuf buf) {
		ByteBufUtils.readUTF8String(buf);
	}

}

 

 

SpeedPacketHandler.java

Spoiler

public class SpeedPacketHandler implements IMessageHandler<SpeedPacket, IMessage> {

	@Override
	public IMessage onMessage(SpeedPacket message, MessageContext ctx) {
		EntityPlayerMP serverPlayer = (EntityPlayerMP) message.p;

		Vec3d lastPos = new Vec3d(serverPlayer.lastTickPosX, serverPlayer.lastTickPosY, serverPlayer.lastTickPosZ);
		Vec3d pos = new Vec3d(serverPlayer.posX, serverPlayer.posY, serverPlayer.posZ);

		RenderUtils.speedDouble = speed(lastPos, pos);

		return null;
	}

	public static Double speed(Vec3d lastPos, Vec3d pos) {
		return Math.abs(lastPos.distanceTo(pos) * 20d);
	}

}

 

 

A little help?

Edited by Prickles
Posted

So your approach is generally correct. So the problem is in the details. To debug issues, you should really sprinkle console statements (system.out.println()) throughout your code in strategic places. For example, if you had such a statement in the onMessage() method you'd be able to confirm whether the message is even being received, and if it is you could see the values in the payload. So I would put in console statements within all the major methods, and wherever you have if statements also put some statements to help confirm which code path is being taken.

 

The only thing that immediately stands out to me is that your event handler class you don't show any event handler annotation. Maybe you have it (you are only posting excerpts) but it is not enough to just register the class, the class also needs the annotation.

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

Posted

Oh, forgot to mention that HelmetHandler#following is called within another method that has a subscribeevent annotation. The RenderGameOverlayEvent.Pre parameter is passed down all the way to RenderUtils#drawNearBox (Organization purposes)

 

As for debugging, it turns out that apparently a player from a List<EntityPlayer> isn't an instance of EntityPlayerMP? How do I cast that correctly?

Posted

Your packet isn't right. You have to remember that the tields in the packet are not transfered in the actual packet but rather used as the source/result for the to bytes and from bytes serialization. In your packet you read back the UUID but don't do anything with it. So other player will always be null. And more importantly you don't send the speed at all. You  need to send that as well.

 

What you need to do I think is take the UUID that you read and then look up the player entity based on that and then store that in your p field.

 

Furthermore, you specifically make p an EntityPlayer so of course that is the type that you'll end up with.

 

However, you code raises another question -- are you trying to display the speed of just the player on that client or are you trying to display speeds over other players that are in view? I thought you were just trying to display the user playerr's speed but it seems you are doing something with a bunch of "chaser" players. In that case what you really need to do is transfer over a map of speeds for all the entities in view. What I would do in that case is to serialize the packet as a map of UUID with speeds. Can you clarify what exactly you're doing with the speed information?

 

In any case it is easiest to just get the client's speed information working first. So I would work on that. 

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

Posted (edited)

1) Well, what this should be doing is iterating through every player in the world, adding them to a list if they are within a certain range, looping through every player on that list, and getting/displaying the speed of each player on that list.

 

2) I thought I need to calculate the speed on the server, not send the speed?

 

3) Whenever I try to cast an EntityPlayer to EntityPlayerMP, I get the following error:

net.minecraft.client.entity.EntityOtherPlayerMP cannot be cast to net.minecraft.entity.player.EntityPlayerMP

 And adding a check to see if the EntityPlayer is an instance of EntityPlayerMP returns false and skips the line that sends the speed packet to the player.

 

4) Is this any better?

 

SpeedPacket.java

Spoiler

public class SpeedPacket implements IMessage {
	public SpeedPacket() {}
	
	private EntityPlayer p;
	
	public SpeedPacket(EntityPlayer p) {
		this.p = p;
	}

	@Override
	public void toBytes(ByteBuf buf) {
		buf.writeDouble(p.lastTickPosX);
		buf.writeDouble(p.lastTickPosY);
		buf.writeDouble(p.lastTickPosZ);
		
		buf.writeDouble(p.posX);
		buf.writeDouble(p.posY);
		buf.writeDouble(p.posZ);
	}

	@Override
	public void fromBytes(ByteBuf buf) {
		p.lastTickPosX = buf.readDouble();
		p.lastTickPosY = buf.readDouble();
		p.lastTickPosZ = buf.readDouble();
		
		p.posX = buf.readDouble();
		p.posY = buf.readDouble();
		p.posZ = buf.readDouble();
	}
	
	public static class SpeedPacketHandler implements IMessageHandler<SpeedPacket, IMessage> {

		@Override
		public IMessage onMessage(SpeedPacket message, MessageContext ctx) {
			EntityPlayer p = message.p;

			Vec3d lastPos = new Vec3d(p.lastTickPosX, p.lastTickPosY, p.lastTickPosZ);
			Vec3d pos = new Vec3d(p.posX, p.posY, p.posZ);
			
			System.out.println(lastPos.toString());
			System.out.println(pos.toString());

			RenderUtils.speedDouble = speed(lastPos, pos);
			
			System.out.println(RenderUtils.speedDouble);

			return null;
		}

		public static Double speed(Vec3d lastPos, Vec3d pos) {
			return Math.abs(lastPos.distanceTo(pos) * 20d);
		}

	}

}

 

 

Excerpt from RenderUtils#drawNearBox

Spoiler

	public static void drawNearBox(RenderGameOverlayEvent.Pre evt, ArrayList<EntityPlayer> l) {
		Minecraft mc = Minecraft.getMinecraft();
		ScaledResolution sr = new ScaledResolution(mc);

		float scale = 0.5f;

		List<String> text = new ArrayList<String>();
		text.add("Near");

		for (EntityPlayer e : l) {
			if (!isNPC(e)) {
				if (mc.world.isRemote && e instanceof EntityPlayerMP) {
					Main.snw.sendTo(new SpeedPacket(e), (EntityPlayerMP) e);
				} else {
					System.out.println("Not an instance of EntityPlayerMP");
				}

				String speed = (Math.round(speedDouble * 10d) / 10d) + " bps";
				String distance = Math.round(mc.player.getDistanceToEntity(e) * 10f) / 10f + "m";

				text.add(e.getName() + " | " + speed + " | " + distance);
			}
		}

(It always prints "Not an instance of EntityPlayerMP")

 

Edited by Prickles
Posted

I think you're still confused about how the information in the packets work. You shouldn't need a player field anywhere in the message. You just need to store the information you want to pass, which in your case should only be the speed.

 

So on the server you should be calculating the speed, putting that in the packet and just sending that and storing it in a double field. But the double field shouldn't be in the message itself but rather some sort of global field that your rendering code can access later. So your fromBytes() method should store the value directly into some global field.

 

Also, your rendering code is messed up as well because that code will only run on the client, but you're trying to send your packets from there. Also, you're looping through all the players but your packet is only containing information about one player.

 

So let me try to break down the way the logic should flow.

1) on the server you should handle the server tick event and do a for loop through all the players and store the UUID and the speed (calculated) in a map type field.

2) on the server you should then cycle through each player and send a message to each player (send to all) that contains all the pairs of UUID and speed. Your toBytes() method should first put an int that indicates the size of the map, then write the UUID, then the speed, for each entry in the map.

3) on the client you should have a global field with same map format (UUID and speed).

4) on the client side you should handle the messages, and your fromBytes() should first read the int for the number of player entries, and then do that many loops to read the UUID and speed pairs into your map from step 3.

5) in your rendering handler you should find list of all players you care about (I guess you want the ones nearby) and then look up their speed based on their UUID from your map.

 

 

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

Posted (edited)

Well, I tried doing that

 

SpeedPacket.java

Spoiler

public class SpeedPacket implements IMessage {	
	public HashMap<UUID, Double> playerMapRaw = new HashMap<UUID, Double>();
	
	public SpeedPacket() {
		for (EntityPlayer e : Minecraft.getMinecraft().world.playerEntities) {
			if (playerMapRaw.size() < NearBox.maxChasers) {
				if (Minecraft.getMinecraft().player.getDistanceSqToEntity(e) < NearBox.warningDist) {
					playerMapRaw.put(e.getUniqueID(), (Math.round(getSpeed(e) * 10d) / 10d));
				}
			}
		}
	}
	
	@Override
	public void toBytes(ByteBuf buf) {		
		buf.writeInt(playerMapRaw.size());
		
		for(int i = 0; i < playerMapRaw.size(); i++) {
			System.out.println(playerMapRaw.keySet().toArray()[i].toString());
			System.out.println(playerMapRaw.values().toArray()[i].toString());
			
			ByteBufUtils.writeUTF8String(buf, playerMapRaw.keySet().toArray()[i].toString());
			buf.writeDouble(getSpeed(Minecraft.getMinecraft().world.getPlayerEntityByUUID((UUID) playerMapRaw.keySet().toArray()[i])));
		}
	}

	@Override
	public void fromBytes(ByteBuf buf) {
		System.out.println("Reading");
		int size = buf.readInt();
		
		for(int i = 0; i < size; i++) {
			NearBox.playerMap.put(UUID.fromString(ByteBufUtils.readUTF8String(buf)), buf.readDouble());
			System.out.println("PlayerMap: " + playerMapRaw.keySet().toArray()[i] + " | " + playerMapRaw.values().toArray()[i]);
		}
	}
	
	public static double getSpeed(EntityPlayer e) {
		Vec3d lastPos = new Vec3d(e.lastTickPosX, e.lastTickPosY, e.lastTickPosZ);
		Vec3d pos = new Vec3d(e.posX, e.posY, e.posZ);
		
		return Math.abs(pos.distanceTo(lastPos) * 20d);
	}
	
	public static class SpeedPacketHandler implements IMessageHandler<SpeedPacket, IMessage> {

		@Override
		public IMessage onMessage(SpeedPacket message, MessageContext ctx) {
			return null;
		}

	}

}

 

 

Excerpt from NearBox#drawNearBox (I combined my rendering method and the HelmetHandler methods)

Spoiler

public static HashMap<UUID, Double> playerMap = new HashMap<UUID, Double>();

	@SubscribeEvent
	public static void drawNearBox(RenderGameOverlayEvent.Pre evt) {
		if (HelmetHelper.isEquipped()) {
			Main.snw.sendToAll(new SpeedPacket());

			if (playerMap.size() > 0) {
				Minecraft mc = Minecraft.getMinecraft();
				ScaledResolution sr = new ScaledResolution(mc);

				float scale = 0.5f;

				List<String> text = new ArrayList<String>();
				text.add("Near");

				for (int i = 0; i < playerMap.size(); i++) {
					EntityPlayer e = mc.world.getPlayerEntityByUUID((UUID) playerMap.keySet().toArray()[i]);
					if (!EntityHelper.isNPC(e)) {
						if (e instanceof EntityPlayerMP) {
							String speed = playerMap.values().toArray()[i] + " bps";
							String distance = Math.round(mc.player.getDistanceToEntity(e) * 10f) / 10f + "m";

							text.add(e.getName() + " | " + speed + " | " + distance);
						} else {
							System.out.println("Not an instance of EntityPlayerMP");
						}
					}
				}

 

 

According to the sys out prints, I got the speeds and UUIDs in toBytes(), however, fromBytes() was never even called ("Reading" from the sys out println in fromBytes() was never printed)

So, I could never update the global hashmap in NearBox with the filled hashmap, because that's in fromBytes()

 

These errors were also spammed in my console:

Spoiler

io.netty.channel.embedded.EmbeddedChannel recordException
WARNING: More than one exception was raised. Will report only the first one and log others.
java.lang.NullPointerException
	at net.minecraftforge.fml.common.network.FMLOutboundHandler$OutboundTarget$5.selectNetworks(FMLOutboundHandler.java:154)

 

 

Spoiler

[17:58:46] [main/INFO] [STDOUT]: [prickles.insightsq.packets.SpeedPacket:toBytes:30]: ea3a379f-d560-4aaf-8bfc-ba46062131ff
[17:58:46] [main/INFO] [STDOUT]: [prickles.insightsq.packets.SpeedPacket:toBytes:31]: 0.0
[17:58:46] [main/ERROR] [FML]: FMLIndexedMessageCodec exception caught
java.lang.NullPointerException: null
	at net.minecraftforge.fml.common.network.FMLOutboundHandler$OutboundTarget$5.selectNetworks(FMLOutboundHandler.java:154) ~[FMLOutboundHandler$OutboundTarget$5.class:?]

 

 

Also, do I even need to use the IMessageHandler?

Edited by Prickles
Posted

Okay, you're getting closer.

 

Your constructor for the packet is wrong though. The packet is supposed to be constructed on the server and sent to the client. However, in your constructor you're using the Minecraft class which is client-side only. Instead it would be better if you passed a world instance as a parameter into the packet constructor and then got the entity list from that, and then when you instantiate a packet pass a the world from the server.

 

However, I think the null pointer is probably another problem. It seems that maybe the network channel isn't set up properly? Do you have a properly registered network channel to send the packet on?

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

Posted (edited)

How would I pass in the server world correctly? What world instance would I use? (mc.player.getServer().getEntityWorld() always returns null, and I can't think of how getting the world from the message handler class could work)

 

And here's my packet initialization (Changed snw to INSTANCE): 

	public static final SimpleNetworkWrapper INSTANCE = NetworkRegistry.INSTANCE.newSimpleChannel(Reference.MODID);
	//...
	//In preInit
	INSTANCE.registerMessage(SpeedPacketHandler.class, SpeedPacket.class, 0, Side.CLIENT); 

 

Edited by Prickles
Posted
3 hours ago, Prickles said:

How would I pass in the server world correctly? What world instance would I use? (mc.player.getServer().getEntityWorld() always returns null, and I can't think of how getting the world from the message handler class could work)

 

You're still confused about how servers and clients work, as well as how packet handling works.

 

There is no such thing as mc on the server. Assuming you mean mc as Minecraft.getMinecraft() that only exists on the client.

 

You're also trying to send a packet from the RenderOverlayEvent which is fired on the client, so that is wrong.

 

Anyway, you're making it too complicated. You simply need to calculate the player speed on the server in some tick event and send a packet from there. Wherever you have a player, you also have the world so use that.

 

There are actually several options for where you choose to execute the code. For example, you could do it on a WorldTick event in which case you would of course have a world available. Or you could do it on a PlayerTick event where you would have world via the player. 

 

Here again are the steps you need to do, with more emphasis on which side the code runs on:

 

On the server, in the world tick handler, confirm it is running on the server side (with !world.isRemote) and if so then:

1)  do a for loop through all the players and store the UUID and the speed (calculated) in a map type field.

2)  send a message to all that you construct by passing the hashmap in as a parameter. Your toBytes() method should first put an int that indicates the size of the map, then write the UUID, then the speed, for each entry in the map.

3) on the client you should have a global field with same map format (UUID and speed).

 

On the client, in your message handler which is registered to client side:

4) your fromBytes() should first read the int for the number of player entries, and then do that many loops to read the UUID and speed pairs to construct a hashmap that you store in a global field (not in the message class but somewhere else static like your main class).

5) in your rendering event handler you should find list of all players you care about (I guess you want the ones nearby) and then look up their speed based on their UUID from your map and do the rendering you want.

 

 

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

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.