Jump to content

Recommended Posts

Posted

Hello all!

 

I was toodling around with a block I had made, and inserted the following code into the Block class for the purposes of figuring out how entity rotation worked. The block, when placed, outputs the raw rotationYaw value of the placing entity to the console, once for the Client and once for the internal Server. The relevant function is below, everything else is standard boilerplate for a basic block class extending Block:

 

 

@Override
public void onBlockPlacedBy(World world, int xPos, int yPos, int zPos, EntityLivingBase placingEntity, ItemStack placingItemStack) {
	float rotation = placingEntity.rotationYaw;
	if (world.isRemote) {
		System.out.println("CLIENT: Placing Entity Rotation: " + rotation);
	}
	if (!world.isRemote) {
		System.out.println("SERVER: Placing Entity Rotation: " + rotation);
	}
}

 

 

However, when run, I noticed something that struck me as odd. As I rotate my character in place, the Client rotationYaw simply increases and increases seemingly forever, whereas the Server rotationYaw seems constrained within 0-360. Logging in and out does not seem to "synchronize" these values, and eventually results in angles that are not coterminal. (The Client rotationYaw minus a multiple of 360 does not always equal the Server rotationYaw). An example log output is below:

 

 

CLIENT: Placing Entity Rotation: -83.243095
SERVER: Placing Entity Rotation: -83.243095
CLIENT: Placing Entity Rotation: -350.84305
SERVER: Placing Entity Rotation: -354.29306
CLIENT: Placing Entity Rotation: -558.8931
SERVER: Placing Entity Rotation: -198.89313
CLIENT: Placing Entity Rotation: -675.1431
SERVER: Placing Entity Rotation: -314.84314
CLIENT: Placing Entity Rotation: -437.54324
SERVER: Placing Entity Rotation: -81.14325
CLIENT: Placing Entity Rotation: -732.5933
SERVER: Placing Entity Rotation: -12.593323
CLIENT: Placing Entity Rotation: -822.1435
SERVER: Placing Entity Rotation: -101.99347
CLIENT: Placing Entity Rotation: -931.04346
SERVER: Placing Entity Rotation: -211.04346
CLIENT: Placing Entity Rotation: -1050.1434
SERVER: Placing Entity Rotation: -330.14343
CLIENT: Placing Entity Rotation: -1141.6433
SERVER: Placing Entity Rotation: -61.64331
CLIENT: Placing Entity Rotation: -1210.4933
SERVER: Placing Entity Rotation: -130.49329
CLIENT: Placing Entity Rotation: -1255.3433
SERVER: Placing Entity Rotation: -175.19324
CLIENT: Placing Entity Rotation: -1327.7931
SERVER: Placing Entity Rotation: -247.04309
CLIENT: Placing Entity Rotation: -1367.2432
SERVER: Placing Entity Rotation: -284.99316
CLIENT: Placing Entity Rotation: -1389.4431
SERVER: Placing Entity Rotation: -309.2931
CLIENT: Placing Entity Rotation: -1442.8433
SERVER: Placing Entity Rotation: -1.0432129
CLIENT: Placing Entity Rotation: -1467.5931
SERVER: Placing Entity Rotation: -27.443115

 

And the same after logging out and in again:

 

CLIENT: Placing Entity Rotation: -377.54337
SERVER: Placing Entity Rotation: -17.543365
CLIENT: Placing Entity Rotation: -411.74344
SERVER: Placing Entity Rotation: -49.043457
CLIENT: Placing Entity Rotation: -469.34338
SERVER: Placing Entity Rotation: -108.74341
CLIENT: Placing Entity Rotation: -571.1934
SERVER: Placing Entity Rotation: -211.0434
CLIENT: Placing Entity Rotation: -619.64343
SERVER: Placing Entity Rotation: -256.79346
CLIENT: Placing Entity Rotation: -677.2434
SERVER: Placing Entity Rotation: -312.89337
CLIENT: Placing Entity Rotation: -757.1934
SERVER: Placing Entity Rotation: -32.843445

 

 

I'm just curious as to how this value is set, whether the client value is at all useful and whether it's simply the convention to test for server/client if rotationYaw is needed.

 

Thank you in advance for your time! I am extremely new to modding in general so I apologize if this is a stupid line of questioning.

 

I am running FML 7.2.156.1060 for Minecraft 1.7.2 on Java 64-Bit Server VM, version 1.6.0_65 on OSX.

Posted

I am facing the same problem, The client sided rotationyaw is increasing during an active session.

I want an affected entity A to be facing an other entity B, so I have to invert the entity A's facing with the facing of entity B.

        String facing[] = 		{"S", "SW", "W", "NW", "N", "NE", "E", "SE"};
        String invertfacing[] = {"N", "NE", "E", "SE", "S", "SW", "W", "NW"};
    	double yaw = ((target.getRotationYawHead()+22.5) % 360); //target being affected entity(A)
    	if (yaw < 0) 
    		yaw = 360 + yaw;

    	System.out.println(String.format("entity A facing: %s| entity B facing: %s", invertfacing[(int)(yaw / 45)],facing[(int)(yaw / 45)])); //

When the entity is facing South, mine will be facing North. This is a very dirty way of setting a rotationyaw. If entity B turns 180 degrees outwards, entity A will do too, making them no longer face eachother. And the whole point of my conception is to make entity A look at entity B.

( I am not using the EntityLookHelper class because it is not available in EntityLivingBase and EntityBodyHelper overrides instances of the EntityLookHelper class)

 

Posted

Hi

 

Looking at 1.6.4. code, the server handler for Packet10 (eg Packet12Look) uses Entity.setRotation

   /**
     * Sets the rotation of the entity
     */
    protected void setRotation(float par1, float par2)
    {
        this.rotationYaw = par1 % 360.0F;
        this.rotationPitch = par2 % 360.0F;
    }

The client frequently sends look update packets to the server, so I am surprised they get significantly out of sync (after you %360.0F, I mean).  You might be seeing a slight synchronisation delay between client & server?  What if you modify your code to

System.out.println("CLIENT: Placing Entity Rotation: " + rotation % 360.0F);

Does the error get bigger and bigger over time or does the server lag behind the client by a reasonably constant amount?

 

@ShaneCraft:

If you want entity B to look at entity A, you need to calculate the angle based on the difference in their positions, eg using Math.atan2(deltax, deltaz).  The rotationYaw of A is not relevant.

 

http://www.mathsisfun.com/geometry/unit-circle.html  (Entity B is at  the centre of the circle, Entity A is the point on the circle.)

 

-TGG

 

 

Posted

Agreed that the "always increasing" versus within 360 rangs shouldn't be any issue because it is common to use modulo to keep in range as in the example TheGreyGhost gave.  In fact, if you're ever comparing angles in computing, for most purposes you should remember to put both into range before the comparison as many methods can let them get out of range.

 

The only weirdness would be the discrepancy mentioned.  Again in the example cited, it takes the rotationYaw as the value (not an increment), and similarly if you look at the sendMotionUpdates() in the EntityClientPlayer class it sends the value (not an increment).  So it seems pretty clear that the client is sending the rotationYaw value directly to server and server is directly updating (with exception of aliasing to the 360 range).  The only possible discrepancy seems like it would be due to lag of however many ticks it might be between update packets.  I do see that the messages go into a queue (rather than being sent immediately) so lag is possible.  However again, when a packet is received it should sync it fully so unless you're continuously moving the Player entity it should sync up faster than human can really notice.

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

Posted

Thanks for the great input!

 

I actually did try the modulo thing before, but I left it out of the OP because I was most curious about the design reason for why the client increments whereas the server is clamped. I just can't figure out why it works this way and was wondering whether anybody with more experience knew.

 

As far as the "non-coterminal" thing: I tried it again, paying closer attention, and this time noticed the only times the angles diverged by any noticeable quantity (still less than 10° usually) was when the block was placed while the player was currently moving, or immediately after a log-in. If I'm reading you right, jabelar, this can be explained by the fact that the client will have the value nearly instantly, while the server is waiting for a packet to place the block, whereupon it checks the entity's rotation?

Posted

Regarding clamped versus non-clamped angles, it is just a style thing although obviously there is a theoretically the problem with unclamped if the game ran for so long with the player spinning in just one direction that it actually overflowed, but that is not a realistic worry since the rotation is a float (so would take over 5x10^27 years of continuous turning). 

 

But in general programming practice clamping the angle is probably more correct/safe. 

 

The one danger with clamping can be if you have to handle the difference between angles or if the direction of change is important.  For example, is going -45degrees clockwise really the same as going 315degrees counter-clockwise?  If you only care about the angle in the moment it is, but what if you had an animation where the entity looked in the direction it was rotating, in that case you'd need to know direction.  And what if you were calculating physics of angular momentum, in that case going more degrees in same time period would have higher angular momentum.

 

Yeah, both diesieben07 and I are saying that in an individual tick the server and client could easily be off by the movement that occurred in the tick, and possibly longer depending on networking delay.  Network packets are asynchronous to the ticks, so there is no guarantee that server and client could be synced on a tick by tick basis.  If the discrepancy was large I'd be worried, but 10degrees sounds quite expected.

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



  • Recently Browsing

    • No registered users viewing this page.
  • Posts

    • It is 1.12.2 - I have no idea if there is a 1.12 pack
    • Okay, but does the modpack works with 1.12 or just with 1.12.2, because I need the Forge client specifically for Minecraft 1.12, not 1.12.2
    • 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() ); } }  
  • Topics

×
×
  • Create New...

Important Information

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