Jump to content

Recommended Posts

Posted (edited)

From what I have read the most reliable way to get the player's motion on a server is to have some prevPos variables, and when the player updates obtain the motion by subtracting its position from the previous position. The code I've used for this is:

protected Entity e;
protected double prevPosX;
protected double prevPosY;
protected double prevPosZ;

@SubscribeEvent(priority= EventPriority.HIGHEST)
public void speedCheck(TickEvent.WorldTickEvent event)
{
    if(event.phase == TickEvent.Phase.START)
    {
        double motionX = this.e.posX - this.prevPosX;
        double motionY = this.e.posY - this.prevPosY;
        double motionZ = this.e.posZ - this.prevPosZ;
        double distance = Math.sqrt(motionX * motionX + motionY * motionY + motionZ * motionZ);
        if (distance >= DataReference.MINIMUM_ENTITY_SPEED) 
        {
            System.out.println(distance);
        }
        if (this.e.isDead)
        {
            MinecraftForge.EVENT_BUS.unregister(this);
        }
        this.prevPosX = this.e.posX;
        this.prevPosY = this.e.posY;
        this.prevPosZ = this.e.posZ;
    }
}

But the distance values that are printed out vary, even though they are printed out while the player is walking straight forward. Here's a sample:

Spoiler

0.4317180760788172
0.4317180760788172
0.4317180760788172
0.4317180760788172
0.41552447515751856
0.42908221391122287
0.43128902593028906
0.6474245332133046
0.4317118609673257
0.6475748941811714
0.43171797770438
1.0792951684334544
0.43171806748241603
0.8634361349739166
1.2951542024638933
0.4317180674880145
0.4317180674880145
1.0792951687200363
0.4317180674880145
0.4317180674880145
0.6475771012320217
0.4317180674880145

 

So the mode value is 0.4317180760788172 but I've seen speeds as high as 1.5 even while walking. Speeds can go lower than the above values as well.

Even the value of 0.43 bothers me, because the wiki lists average walk speed as 4.3 metres per second. With 20 ticks to a second I assume the value I should be getting is 0.215.

 

So where have I gone wrong? If I had to guess, it's because of not taking skipped ticks into account; in which case how can I get the number of ticks skipped to divide by that number?

Thanks for reading.

Edited by FredTargaryen
Posted

That works for every Entity I've tried except the player. The motion of the player seems to update on the client but not the server. I've tried sending packets with the player's motion but it's too slow for what I want :/

Posted

What do you mean by not taking skpped ticks into account? Are you talking about when there is lag? People argue about it but most end up agreeing that if the whole game is slowed down then you should scale accordingly -- if the ticks are only 10 per second then the player speed should be half as much.

 

If you really wanted to calibrate to real time that is fairly easy to do though. Minecraft is just a Java program and Java has various methods for checking real-time. So you can create a timer that checks for difference in system time on each server tick and if it varies from the expected 1/20th of a second you can set a scale factor you can use for calculations during that tick.

  • Like 1

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

Posted

Yeah, I only have a potato to develop on so I often get "Server is 23123s behind, skipping 1024 ticks" or words to that effect. I thought if ticks were being skipped then PlayerTickEvents would not fire so the prevPos values weren't being updated, so the speed would look faster when the next PlayerTickEvent fired. Maybe that doesn't quite make sense but I was just guessing.

 

OK, I'll set up a timer and see what happens. Thanks!

  • 2 weeks later...
Posted

I made a timer and after checking with that, realised that I'm not actually skipping any ticks. I removed a minimum distance that I had put into the code and realised the mode distance travelled each tick is actually about 0.215, which is consistent with the wiki.

 

However during a normal forward walk, the distance in one tick can still sometimes be set to 0.43 or higher, which is faster than sprinting. This is bad for my code because ideally I should be able to check if the player is definitely moving at sprinting speed or faster in just one tick. My best guess is that the server occasionally moves the player ahead further than their movement suggests, in which case I'm at a loss as to what to do about it (I probably shouldn't be doing anything about it).

 

I could keep track of the mean speed over several ticks but these outliers are so high that the mean might still be faster than sprinting.

I'm thinking about tracking the mode of the last few tick distances, and assuming the mode distance if the latest distances are out of line with the mode. This probably gives me the best chance of getting the right speed but it does mean a change from walking to sprinting, for example, wouldn't register for a few ticks. If anyone has any better ideas I'm all ears.

Posted

This is due to the fact that the client does not inform the server about it's position change every tick, but rather once every couple of ticks.

  • Like 1

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.

Posted

So does the server not move players around at all? The position only updates on the server when the client tells it to update? I assumed if the player was falling for example the server would process that without waiting for client info.

Posted
3 hours ago, FredTargaryen said:

So does the server not move players around at all? The position only updates on the server when the client tells it to update? I assumed if the player was falling for example the server would process that without waiting for client info.

The client takes input, converts that to motion, and sends position updates to the server.

 

Yes.

  • Like 1

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.

Posted

So I suppose sending my own packets from the client each tick as as accurate as I can get then. Just gotta pray it doesn't prove too intensive. Thank you all for your answers so far; not marking as solved just yet in case I have a brainwave.

Posted

Are you trying to make some sort of anti-cheat? What are you looking to do with the player's speed? I'm just wondering, because if it's not for something like an anti-cheat or a live speed meter I'd imagine there might be more elegant sollutions than sending a packet to the server every tick for every online player.

 

Have you considered measuring the average speed client side by calculating the position every X ticks and then sending that average speed to the server? as said above the client has more accurate 'live' information on position. If you don't need something to happen the exact tick your movement speed drops below 4.3m/s that could very well work.

  • Like 1
Posted

It's not an anti-cheat but I need to check speed every tick, or as near as possible, because I want blocks to respond to the player speed before the player can collide with them. I didn't want the heavy traffic of movement packets, or to have to deal with the latency of packets being sent, so I tried to make it all server-side, but as you can see that didn't work.

 

Yeah, sending the average speed every few ticks sounds much saner than every tick. I will try it.

Posted

Have you thought about possibly making this part of the block rather than the player sending updates to the server? If you want a specific block to move out of the way when a player runs into it you could make the tile Entity search for players within an X radius of it. Then when the player gets near it could look at average speed and react to it. Depending on what block behaviour you want this wouldn't even have to be incredibly precise.  

 

that way blocks could react to entities being within X meters of the block rather than players sending a continuous signal to the server. Ofcourse both of those ideas have their merit

  • Like 1
Posted

I did used to have this as a block as you described but for various reasons, a lot of which I can't remember, I decided it would be best to make this a player thing. The main reason is I'm making fragile glass blocks, so I would expect players to use them like glass blocks, i.e. TileEntities all over the place if they make a big building or window. Those TileEntities would be constantly checking for players even if none were close enough, and if they're next to each other they'd be unnecessarily checking the same player twice or more.

Luckily I have my version control so I can branch if I don't get anywhere with this, but I am getting a much better effect this way for every entity... except players.

 

I'm thinking of only sending a message when the player changes speed significantly... but I need to see some client motion values first, and if they're as unpredictable as the server that won't really be possible.

Posted

So in light of the fact that the client is the only good source of speed info, I have found a compromise to this problem which works pretty well. When an AttachCapabilitiesEvent is fired for the player, if the player's world is remote I construct a listener object and register it with the Forge Event Bus (if not remote I attach the capability). Each tick the listener calculates the speed of the player and compares it with the previous speed. If the current speed differs from the previous speed by more than 0.01, only then do I send a packet (there is still variation on the client but it is extremely subtle compared to the server) and update the previous speed. The packet then updates the speed on the Capability on the server. So on the server, instead of trying to calculate the speed from player variables, the last speed sent from the client is used. To account for the latency of sending the update packet I run the break code by simulating collisions ahead of where the player will be next tick, so the blocks break as if the player has been moving for three ticks instead of one. Most of the time this produces the effect I was looking for. Thank you to everyone for your help and advice!

 

Code:

@SubscribeEvent
    public void onBreakerConstruct(AttachCapabilitiesEvent<Entity> evt)
    {
        final Entity e = evt.getObject();
        if(e.world.isRemote)
        {
            if(e instanceof EntityPlayer)
            {
                MinecraftForge.EVENT_BUS.register(new Object() {
                    private EntityPlayer ep = (EntityPlayer) e;
                    private double lastSpeed;
                    @SubscribeEvent(priority=EventPriority.HIGHEST)
                    public void speedUpdate(TickEvent.ClientTickEvent event)
                    {
                        if(event.phase == TickEvent.Phase.END)
                        {
                            double speed = Math.sqrt(ep.motionX * ep.motionX + ep.motionY * ep.motionY + ep.motionZ * ep.motionZ);
                            if(Math.abs(speed - this.lastSpeed) > 0.01)
                            {
                                MessageBreakerMovement mbm = new MessageBreakerMovement();
                                mbm.motionx = ep.motionX;
                                mbm.motiony = ep.motionY;
                                mbm.motionz = ep.motionZ;
                                mbm.speed = speed;
                                PacketHandler.INSTANCE.sendToServer(mbm);
                                this.lastSpeed = speed;
                            }
                            if(ep.isDead)
                            {
                                MinecraftForge.EVENT_BUS.unregister(this);
                            }
                        }
                    }
                    @SubscribeEvent
                    public void killObject(FMLNetworkEvent.ClientDisconnectionFromServerEvent event)
                    {
                        MinecraftForge.EVENT_BUS.unregister(this);
                    }
            });
            }
          ...
}

 

For a ridiculous block comment thesis about my block break system you can see here.

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



  • Recently Browsing

    • No registered users viewing this page.
  • Posts

    • Version 1.19 - Forge 41.0.63 I want to create a wolf entity that I can ride, so far it seems to be working, but the problem is that when I get on the wolf, I can’t control it. I then discovered that the issue is that the server doesn’t detect that I’m riding the wolf, so I’m struggling with synchronization. However, it seems to not be working properly. As I understand it, the server receives the packet but doesn’t register it correctly. I’m a bit new to Java, and I’ll try to provide all the relevant code and prints *The comments and prints are translated by chatgpt since they were originally in Spanish* Thank you very much in advance No player is mounted, or the passenger is not a player. No player is mounted, or the passenger is not a player. No player is mounted, or the passenger is not a player. No player is mounted, or the passenger is not a player. No player is mounted, or the passenger is not a player. MountableWolfEntity package com.vals.valscraft.entity; import com.vals.valscraft.network.MountSyncPacket; import com.vals.valscraft.network.NetworkHandler; import net.minecraft.client.Minecraft; import net.minecraft.network.syncher.EntityDataAccessor; import net.minecraft.network.syncher.EntityDataSerializers; import net.minecraft.network.syncher.SynchedEntityData; import net.minecraft.server.MinecraftServer; import net.minecraft.server.level.ServerPlayer; import net.minecraft.world.entity.EntityType; import net.minecraft.world.entity.Mob; import net.minecraft.world.entity.ai.attributes.AttributeSupplier; import net.minecraft.world.entity.ai.attributes.Attributes; import net.minecraft.world.entity.animal.Wolf; import net.minecraft.world.entity.player.Player; import net.minecraft.world.entity.Entity; import net.minecraft.world.InteractionHand; import net.minecraft.world.InteractionResult; import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.Items; import net.minecraft.world.level.Level; import net.minecraft.world.phys.Vec3; import net.minecraftforge.event.TickEvent; import net.minecraftforge.eventbus.api.SubscribeEvent; import net.minecraftforge.network.PacketDistributor; public class MountableWolfEntity extends Wolf { private boolean hasSaddle; private static final EntityDataAccessor<Byte> DATA_ID_FLAGS = SynchedEntityData.defineId(MountableWolfEntity.class, EntityDataSerializers.BYTE); public MountableWolfEntity(EntityType<? extends Wolf> type, Level level) { super(type, level); this.hasSaddle = false; } @Override protected void defineSynchedData() { super.defineSynchedData(); this.entityData.define(DATA_ID_FLAGS, (byte)0); } public static AttributeSupplier.Builder createAttributes() { return Wolf.createAttributes() .add(Attributes.MAX_HEALTH, 20.0) .add(Attributes.MOVEMENT_SPEED, 0.3); } @Override public InteractionResult mobInteract(Player player, InteractionHand hand) { ItemStack itemstack = player.getItemInHand(hand); if (itemstack.getItem() == Items.SADDLE && !this.hasSaddle()) { if (!player.isCreative()) { itemstack.shrink(1); } this.setSaddle(true); return InteractionResult.SUCCESS; } else if (!level.isClientSide && this.hasSaddle()) { player.startRiding(this); MountSyncPacket packet = new MountSyncPacket(true); // 'true' means the player is mounted NetworkHandler.CHANNEL.sendToServer(packet); // Ensure the server handles the packet return InteractionResult.SUCCESS; } return InteractionResult.PASS; } @Override public void travel(Vec3 travelVector) { if (this.isVehicle() && this.getControllingPassenger() instanceof Player) { System.out.println("The wolf has a passenger."); System.out.println("The passenger is a player."); Player player = (Player) this.getControllingPassenger(); // Ensure the player is the controller this.setYRot(player.getYRot()); this.yRotO = this.getYRot(); this.setXRot(player.getXRot() * 0.5F); this.setRot(this.getYRot(), this.getXRot()); this.yBodyRot = this.getYRot(); this.yHeadRot = this.yBodyRot; float forward = player.zza; float strafe = player.xxa; if (forward <= 0.0F) { forward *= 0.25F; } this.flyingSpeed = this.getSpeed() * 0.1F; this.setSpeed((float) this.getAttributeValue(Attributes.MOVEMENT_SPEED) * 1.5F); this.setDeltaMovement(new Vec3(strafe, travelVector.y, forward).scale(this.getSpeed())); this.calculateEntityAnimation(this, false); } else { // The wolf does not have a passenger or the passenger is not a player System.out.println("No player is mounted, or the passenger is not a player."); super.travel(travelVector); } } public boolean hasSaddle() { return this.hasSaddle; } public void setSaddle(boolean hasSaddle) { this.hasSaddle = hasSaddle; } @Override protected void dropEquipment() { super.dropEquipment(); if (this.hasSaddle()) { this.spawnAtLocation(Items.SADDLE); this.setSaddle(false); } } @SubscribeEvent public static void onServerTick(TickEvent.ServerTickEvent event) { if (event.phase == TickEvent.Phase.START) { MinecraftServer server = net.minecraftforge.server.ServerLifecycleHooks.getCurrentServer(); if (server != null) { for (ServerPlayer player : server.getPlayerList().getPlayers()) { if (player.isPassenger() && player.getVehicle() instanceof MountableWolfEntity) { MountableWolfEntity wolf = (MountableWolfEntity) player.getVehicle(); System.out.println("Tick: " + player.getName().getString() + " is correctly mounted on " + wolf); } } } } } private boolean lastMountedState = false; @Override public void tick() { super.tick(); if (!this.level.isClientSide) { // Only on the server boolean isMounted = this.isVehicle() && this.getControllingPassenger() instanceof Player; // Only print if the state changed if (isMounted != lastMountedState) { if (isMounted) { Player player = (Player) this.getControllingPassenger(); // Verify the passenger is a player System.out.println("Server: Player " + player.getName().getString() + " is now mounted."); } else { System.out.println("Server: The wolf no longer has a passenger."); } lastMountedState = isMounted; } } } @Override public void addPassenger(Entity passenger) { super.addPassenger(passenger); if (passenger instanceof Player) { Player player = (Player) passenger; if (!this.level.isClientSide && player instanceof ServerPlayer) { // Send the packet to the server to indicate the player is mounted NetworkHandler.CHANNEL.send(PacketDistributor.PLAYER.with(() -> (ServerPlayer) player), new MountSyncPacket(true)); } } } @Override public void removePassenger(Entity passenger) { super.removePassenger(passenger); if (passenger instanceof Player) { Player player = (Player) passenger; if (!this.level.isClientSide && player instanceof ServerPlayer) { // Send the packet to the server to indicate the player is no longer mounted NetworkHandler.CHANNEL.send(PacketDistributor.PLAYER.with(() -> (ServerPlayer) player), new MountSyncPacket(false)); } } } @Override public boolean isControlledByLocalInstance() { Entity entity = this.getControllingPassenger(); return entity instanceof Player; } @Override public void positionRider(Entity passenger) { if (this.hasPassenger(passenger)) { double xOffset = Math.cos(Math.toRadians(this.getYRot() + 90)) * 0.4; double zOffset = Math.sin(Math.toRadians(this.getYRot() + 90)) * 0.4; passenger.setPos(this.getX() + xOffset, this.getY() + this.getPassengersRidingOffset() + passenger.getMyRidingOffset(), this.getZ() + zOffset); } } } MountSyncPacket package com.vals.valscraft.network; import com.vals.valscraft.entity.MountableWolfEntity; import net.minecraft.network.FriendlyByteBuf; import net.minecraft.server.level.ServerLevel; import net.minecraft.server.level.ServerPlayer; import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.player.Player; import net.minecraftforge.network.NetworkEvent; import java.util.function.Supplier; public class MountSyncPacket { private final boolean isMounted; public MountSyncPacket(boolean isMounted) { this.isMounted = isMounted; } public void encode(FriendlyByteBuf buffer) { buffer.writeBoolean(isMounted); } public static MountSyncPacket decode(FriendlyByteBuf buffer) { return new MountSyncPacket(buffer.readBoolean()); } public void handle(NetworkEvent.Context context) { context.enqueueWork(() -> { ServerPlayer player = context.getSender(); // Get the player from the context if (player != null) { // Verifies if the player has dismounted if (!isMounted) { Entity vehicle = player.getVehicle(); if (vehicle instanceof MountableWolfEntity wolf) { // Logic to remove the player as a passenger wolf.removePassenger(player); System.out.println("Server: Player " + player.getName().getString() + " is no longer mounted."); } } } }); context.setPacketHandled(true); // Marks the packet as handled } } networkHandler package com.vals.valscraft.network; import com.vals.valscraft.valscraft; import net.minecraft.resources.ResourceLocation; import net.minecraftforge.network.NetworkRegistry; import net.minecraftforge.network.simple.SimpleChannel; import net.minecraftforge.network.NetworkEvent; import java.util.function.Supplier; public class NetworkHandler { private static final String PROTOCOL_VERSION = "1"; public static final SimpleChannel CHANNEL = NetworkRegistry.newSimpleChannel( new ResourceLocation(valscraft.MODID, "main"), () -> PROTOCOL_VERSION, PROTOCOL_VERSION::equals, PROTOCOL_VERSION::equals ); public static void init() { int packetId = 0; // Register the mount synchronization packet CHANNEL.registerMessage( packetId++, MountSyncPacket.class, MountSyncPacket::encode, MountSyncPacket::decode, (msg, context) -> msg.handle(context.get()) // Get the context with context.get() ); } }  
    • Do you use features of inventory profiles next (ipnext) or is there a change without it?
    • Remove rubidium - you are already using embeddium, which is a fork of rubidium
  • Topics

×
×
  • Create New...

Important Information

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