Jump to content

[1.7.10] How to properly sync entity from server to client [Solved]


qb_cakes

Recommended Posts

Hello, been trying to learn recently and network has been stalling my progress for some time now. I've been avoiding it but I don't think I can much more

Anyway, basically, when any entity dies, the server will listen to that event and change a value inside an entity, this value should then be updated to the client

Maybe, probably, the problem isn't really in the network code. But I'd appreciate feedback on it since I'm not sure if I'm doing it properly

 

This is the intended workflow:

 

1. A player spawns an entity in the world, EntityBank

2. A death event occurs - onEntityLivingDeath(LivingDeathEvent event) is called

2.1 Inside the method for the event, (which I noticed is only called in the server), the server checks for entities inside a radius around the position where the death occurred, and if it finds an instance of EntityBank, it calls the method entityBank.add(x);

3. The method entityBank.add(int i) increments a value inside EntityBank called storage. (this value is saved/loaded to nbt)

4. So far it's all good, everything happened well on the server side, but now the client is supposed to know about all this, I made Message and MessageHandler classes to communicate the change to all players, and I believe the issue is here.

 

Cleaned up code: (to show what is relevant)

 

EntityBank:

public class EntityBank extends EntityFlying {

private int stored;

public EntityBank(World world) {
	super(world);
	setSize(0.5f, 0.5f);
}

public void add(int inc) {
	stored += inc;

	System.out.println("~~~ storage was increased by " + inc);
}

public void updateClients() {
	NBTTagCompound ntc = new NBTTagCompound();
	ntc.setInteger("s", stored);
	ntc.setInteger("e", getEntityId());
	StorageMessage message = new StorageMessage(ntc);

	System.out.println("Sending message to clients.");
	MyMod.networkWrapper.sendToAll(message);
}


@Override
protected boolean interact(EntityPlayer player) {
	System.out.println(stored); // server prints the correct value, client doesn't (after restarting the server/world)
	return false;
}

@Override
public void readEntityFromNBT(NBTTagCompound ntc) {
	super.readEntityFromNBT(ntc);

	stored = ntc.getInteger("stored");
}

@Override
public void writeEntityToNBT(NBTTagCompound ntc) {
	super.writeEntityToNBT(ntc);

	ntc.setInteger("stored", stored);
}
}

 

The EventHandler:

public class EventHandler {

private static final float DEATH_RANGE = 10f;

@SubscribeEvent
public void onEntityLivingDeath(LivingDeathEvent event) {
	Entity entity = event.entity;

	if (entity instanceof EntityLiving) {
		int inc = 10; // ...

		if (inc > 0) {
			AxisAlignedBB radiusBB = AxisAlignedBB.getBoundingBox
					(entity.posX - DEATH_RANGE, entity.posY - DEATH_RANGE, entity.posZ - DEATH_RANGE,
							entity.posX + DEATH_RANGE, entity.posY + DEATH_RANGE, entity.posZ + DEATH_RANGE);

			Entity closestEntity = Utils.selectNearestEntityWithinAABB(entity.worldObj, Entity.class, radiusBB, entity,
					(ent) -> ent instanceof EntityBank);

			if (closestEntity != null) {
				System.out.println("Found an EntityBank nearby... adding to storage: " + inc);

				((EntityBank) closestEntity).add(inc);
				((EntityBank) closestEntity).updateClients();
			}
		}
	}
}

}

 

StorageMessage (probably not important?)

public class StorageMessage implements IMessage {

public NBTTagCompound stackTagCompound;

public StorageMessage() {

}

public StorageMessage(NBTTagCompound stackTagCompound) {
	this.stackTagCompound = stackTagCompound;
}

@Override
public void fromBytes(ByteBuf buf) {
	stackTagCompound = ByteBufUtils.readTag(buf);
}

@Override
public void toBytes(ByteBuf buf) {
	ByteBufUtils.writeTag(buf, stackTagCompound);
}
}

 

StorageMessageHandler

public class StorageMessageHandler implements IMessageHandler<StorageMessage, IMessage> {

@Override
public IMessage onMessage(StorageMessage message, MessageContext ctx) {
	NBTTagCompound ntc = message.stackTagCompound;
	int id = ntc.getInteger("e");

	System.out.println("Received storage message!"); // this gets printed

	for (Object obj : Minecraft.getMinecraft().theWorld.loadedEntityList) {
		Entity ent = (Entity) obj;

		if (id == ent.getEntityId()) {
			System.out.println("Entity found! Updating storage..."); // all good here too
			((EntityBank) ent).add(ntc.getInteger("s"));
			break;
		}
	}

	return null;
}
}

 

The network message/handler etc is all registered.

The client does receive the message, the entity gets update (if I interact with the entity, it shows the correct stored value)

but as soons as I leave the world (shutting down the server) and re-enter it again, the information on the client side is no longer in accordance to the server.

I'm probably missing something really stupid, just have no idea what it is, have only got to modding recently too.

Why does the value on the client side reset when I rejoin? Shouldn't things that are loaded on NBT be passed back to the client when he joins the world so he gets the updated information on an entity? What should happen here?

 

And lastly, what do you think of my network code? It looks a bit network intensive/inneficient to me

 

Any help is appreciated

Link to comment
Share on other sites

Keeping it very short.

Facts:

1. Data:

- NBT loading/saving is server side.

- If you want to have ANY data on client, you need to send it.

 

2. Existance of entities:

- Server knows about all currently loaded entities.

- Client knows only about entities (creates them) that server tells it to spawn, simplier:

* Client has only one world (in which player is in).

* Client has only entities that are visible for given player in this world.

 

3. When synchronization (of entities) occurs:

3.1. Server tells client to spawn some entitiy that will be mirror of server's one.

3.2. Server changes values and sends them to players that are currently tracking entity.

 

How? Where? (important points)

 

1. When entity is spawned (spawn packet is sent to client) and it implements IEntityAdditionalSpawnData you can send additional data within spawn packet.

 

2. There is this thing called EntityTracker, in simple words: it keeps track of which player watches which entity. (or other way around, which entity is watched by which players, those are internals, don't matter)

2.1. Whenever a player starts tracking new entity (meaning his client should be notified about entity's existance) - PlayerEvent.StartTracking event is called. It allows you to send additional data that cannot be placed in spawn packed (e.g for vanilla mobs).

2.2. Whenever something about your data changes you also need to notify clients. You do that with mentioned EntityTracker:

EntityTracker et = ((WorldServer) entity.worldObj).getEntityTracker(); // You get EntityTracker of world in which there is entity you want to synchronize
et.func_151248_b(entity, SimpleNetworkWrapper#getInstance().getPacketFrom(new SomeDataPacket()); // You need SNW instance, the method might vari between versions. The method itself says "send this packet to everyone who is tracking this "entity".

Code above will result in packet about "entity" being sent to all clients (players) that are currently "seeing" it (tracking).

Do note that if "entity" is EntityPlayer, the player himself will also be notified (player tracks himself), unless you use other method that is provided in EntityTracker (idk the name).

 

3. SimpleNetworkWrapper methods - there are things like sendTo(player, packed), etc. Those will be used if you want only specific clients to know stuff about entity. Can be used when data is private.

 

That is all you will need, there are also DataWatchers (for simple data only), but I never liked them, so let's skip them.

 

Setting up network is nothing more than making IMessage and Hanlder + SimpleNetworkWrapper.

http://www.minecraftforum.net/forums/mapping-and-modding/mapping-and-modding-tutorials/2137055-1-7-2-customizing-packet-handling-with

 

EDIT

Edited, recommeded to re-read.

 

EDIT EDIT

For god's sake - UPDATE to 1.8+ ! And then:

http://greyminecraftcoder.blogspot.com.au/2015/01/thread-safety-with-network-messages.html

 

Also: 1.8 is better with dynamic updating, scheluding takes care of very annoying data-losses if you are working with (very) dynamic synchronizations.

(E.g: You send packet, but client doesn't have entity constructed yet, boom - data is lost).

1.7.10 is no longer supported by forge, you are on your own.

Link to comment
Share on other sites

Wow, thanks a lot for the information!

So I already have a SimpleNetworkManager instance to use

But I don't understand where will the client listen to these packets

I mean, how will it read the packet and assign the needed values from the packet and assign to the entity?

I'm reading on how to construct the packet yet, but I already have an IMessage implementation (code in the original post), will I be passing data around with NBTs too? I already have done message passing/handling in the original code above, is that what I use to send the packet?

 

And I can't update to 1.8 due to compatibility with other mods that still haven't done so unfortunately  :P

 

Edit: Now I got what you meant with the EntityTracket code section! I just reused the same Message I was using and it worked well

Thanks !

 

Edit2: Implemented IEntityAdditionalSpawnData and it worked exactly as intended

	@Override
public void writeSpawnData(ByteBuf buffer) {
	NBTTagCompound ntc = new NBTTagCompound();
	ntc.setInteger("stored", stored);
	ByteBufUtils.writeTag(buffer, ntc);

	// this should be faster instead
	//buffer.writeInt(stored);
}

@Override
public void readSpawnData(ByteBuf additionalData) {
	NBTTagCompound ntc = ByteBufUtils.readTag(additionalData);
	stored = ntc.getInteger("stored");

	// this should be faster instead
	//stored = additionalData.readInt();
}

you really saved me with all this wizardry

 

Renaming thread to solved

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.