Jump to content

Recommended Posts

Posted

I am having some trouble with a tile entity.

I am using nbt to store a data value but it seems nbt is only read on the server and i need this data on the client aswell can someone tell me how to synchronize the client with the server when the nbt is read?

 

this is my tile entity class

 

  Reveal hidden contents

 

 

the field i am trying to save is "charges"

I am the author of Draconic Evolution

Posted

NBT is mostly for saving and loading information either when game starts/quits or when chunks get loaded/unloaded.

 

Syncing client with server is entirely different.  The client side and the server side (as far as I understand) are pretty much separate programs and don't know anything about the classes in the other side.  So you need to send packets.

 

It is a little bit complicated, but there are a few tutorials out there.  I recently just learned it myself -- I had a render animation that I needed to do on the client but the information was based on entity AI (which runs on the server).

 

Here's how I did it (trust me, my way is one of the more simple, but feel free to look at other tutorials).  I used a system that took advantage of the FMLProxyPacket class and subscribed to some available packet events.

 

Basically, you’ll need a packet system that:

a. Registers a networking channel specific for your mod (this allows you to ignore packets that may come from other mods)

b. Has packet handler classes with Subscribes to the onClientPacketReceived and the onServerPacketReceived events.

c. Has a packet class that can distinguish packet type and what side it is on and process the packet accordingly

d. Sends the packets as needed (ideally just when there are changes, which is often in a setter method).

 

(In my examples, my mod is called WildAnimals -- wherever you see that you should replace with something relevant to your mod.)

Register a networking channel:

  - in your mod's main class create variables:

 

    
    public static final String networkChannelName = "WildAnimals"; // put the name of your mod here
public static FMLEventChannel channel;

  - in your mod's init event handler method (probably in your proxy class) you need:

		WildAnimals.channel = NetworkRegistry.INSTANCE.newEventDrivenChannel(WildAnimals.networkChannelName); // replace WildAnimals with the name of your main class

 

Create and Register Your Packet Handler Classes:

  - also in same init event handler method register your packet handler classes.  In the common proxy init() you need:

		WildAnimals.channel.register(new ServerPacketHandler());  // replace WildAnimals with name of your main class

  - and in your client proxy init() you need to additionally register the packet handler class:

		WildAnimals.channel.register(new ClientPacketHandler()); // replace WildAnimals with name of your main class

 

Create a new class called ServerPacketHandler which contains subscription to a server packet event with something like:

package wildanimals.networking;

import java.io.IOException;

import wildanimals.WildAnimals;
import wildanimals.network.entities.PacketWildAnimals;
import cpw.mods.fml.common.eventhandler.SubscribeEvent;
import cpw.mods.fml.common.network.FMLNetworkEvent.ServerCustomPacketEvent;

public class ServerPacketHandler 
{
protected String channelName;

@SubscribeEvent
public void onServerPacket(ServerCustomPacketEvent event) throws IOException 
{
	channelName = event.packet.channel();

	if (channelName == WildAnimals.networkChannelName)
	{
		PacketWildAnimals.processPacketOnServerSide(event.packet.payload(), event.packet.getTarget());
	}
}
}

 

And create a ClientPacketHandler class that extends the ServerPacketHandler class and subscribes to a client packet event:

package wildanimals.networking;

import java.io.IOException;

import wildanimals.WildAnimals;
import wildanimals.network.entities.PacketWildAnimals;
import cpw.mods.fml.common.eventhandler.SubscribeEvent;
import cpw.mods.fml.common.network.FMLNetworkEvent.ClientCustomPacketEvent;

// Remember client run configuration includes server side too
public class ClientPacketHandler extends ServerPacketHandler
{
@SubscribeEvent
public void onClientPacket(ClientCustomPacketEvent event) throws IOException 
{
	channelName = event.packet.channel();

	if (channelName == WildAnimals.networkChannelName)
	{
		PacketWildAnimals.processPacketOnClientSide(event.packet.payload(), event.packet.getTarget());
	}
}
}

 

Create a Packet Class

Create a packet class that allows you to generate and process received packets as needed.  Note that the actual data you create and receive will need to be changed for your particular need.  Hopefully you can understand how my example packet could be modified for your need.

 

There are a few things to understand -- a packet has a payload that consists of a byte stream.  It is totally up to you what bytes you put in the packet, but you have to come up with a useful format.  I expect to have other packet types so my first data sent consists of a packet type identifier.  I know it is a packet from my mod because of the channel it comes on (I check for the channel).  After that, I want to be able to put information about any of my entities into the packet so my next information is the entityID.  But since I have multiple types of entities, I have to get the class from the entityID and then change my processing based on that.  In that processing I simply take the information I want to send and put it in.  The key thing is to read back the data in the same order that you put it in when you create the packet.

 

package wildanimals.network.entities;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufInputStream;
import io.netty.buffer.ByteBufOutputStream;
import io.netty.buffer.Unpooled;

import java.io.IOException;

import net.minecraft.client.Minecraft;
import net.minecraft.entity.Entity;
import net.minecraft.world.World;
import wildanimals.WildAnimals;
import wildanimals.entities.bigcats.EntityBigCat;
import wildanimals.entities.herdanimals.EntityHerdAnimal;
import wildanimals.entities.serpents.EntitySerpent;
import cpw.mods.fml.common.network.internal.FMLProxyPacket;
import cpw.mods.fml.relauncher.Side;

// this class is intended to be sent from server to client to keep custom entities synced
public class PacketWildAnimals
{
// define IDs for custom packet types
public final static int packetTypeIDEntity = 1;

public PacketWildAnimals()
{
	// don't need anything here
}

public static FMLProxyPacket createEntityPacket(Entity parEntity) throws IOException
{
	// DEBUG
	System.out.println("Sending PacketWildAnimals on Server Side");

	ByteBufOutputStream bbos = new ByteBufOutputStream(Unpooled.buffer());

	// create payload by writing to data stream
	// first identity packet type
	bbos.writeInt(packetTypeIDEntity);

	// write entity instance id (not the class registry id!)
	bbos.writeInt(parEntity.getEntityId());

	// now write entity-specific custom fields
	// process herd animals
	if (parEntity instanceof EntityHerdAnimal)
	{
		EntityHerdAnimal entityHerdAnimal = (EntityHerdAnimal)parEntity;
		bbos.writeFloat(entityHerdAnimal.getScaleFactor());
		bbos.writeBoolean(entityHerdAnimal.isRearing());			
	}
	// process serpents
	else if (parEntity instanceof EntitySerpent)
	{
		EntitySerpent entitySerpent = (EntitySerpent)parEntity;
		bbos.writeFloat(entitySerpent.getScaleFactor());
	}
	// process big cats
	else if (parEntity instanceof EntityBigCat)
	{
		EntityBigCat entityBigCat = (EntityBigCat)parEntity;
		bbos.writeFloat(entityBigCat.getScaleFactor());
	}

	// put payload into a packet		
	FMLProxyPacket thePacket = new FMLProxyPacket(bbos.buffer(), WildAnimals.networkChannelName);

	// don't forget to close stream to avoid memory leak
	bbos.close();

	return thePacket;
}

public static void processPacketOnClientSide(ByteBuf parBB, Side parSide) throws IOException
{
	if (parSide == Side.CLIENT)
	{
		// DEBUG
		System.out.println("Received PacketWildAnimals on Client Side");

		World theWorld = Minecraft.getMinecraft().theWorld;
		ByteBufInputStream bbis = new ByteBufInputStream(parBB);

		// process data stream
		// first read packet type
		int packetTypeID = bbis.readInt();

		switch (packetTypeID)
		{
			case packetTypeIDEntity:  // a packet sent from server to sync entity custom fields
			{
				// find entity instance
				int entityID = bbis.readInt();
				// DEBUG
				System.out.println("Entity ID = "+entityID);
				Entity foundEntity = getEntityByID(entityID, theWorld);
				// DEBUG
				System.out.println("Entity Class Name = "+foundEntity.getClass().getSimpleName());

				// process based on type of entity class
				// process herd animals
				if (foundEntity instanceof EntityHerdAnimal)
				{
					EntityHerdAnimal foundEntityHerdAnimal = (EntityHerdAnimal)foundEntity;
					// apply custom fields to entity instance
					foundEntityHerdAnimal.setScaleFactor(bbis.readFloat());
					foundEntityHerdAnimal.setRearing(bbis.readBoolean());
					// DEBUG
					System.out.println("Is rearing = "+foundEntityHerdAnimal.isRearing());
				}
				// process serpents
				else if (foundEntity instanceof EntitySerpent)
				{
					EntitySerpent foundEntitySerpent = (EntitySerpent)foundEntity;
					// apply custom fields to entity instance
					foundEntitySerpent.setScaleFactor(bbis.readFloat());
				}
				// process big cats
				else if (foundEntity instanceof EntityBigCat)
				{
					EntityBigCat foundEntityBigCat = (EntityBigCat)foundEntity;
					// apply custom fields to entity instance
					foundEntityBigCat.setScaleFactor(bbis.readFloat());
				}
				break;
			}
		}

		// don't forget to close stream to avoid memory leak
		bbis.close();			
	}
}

public static void processPacketOnServerSide(ByteBuf payload, Side parSide) 
{
	if (parSide == Side.SERVER)
	{
		// currently haven't defined any packets from client

	}
}

// some helper functions
public static Entity getEntityByID(int entityID, World world)        
{         
	for(Object o: world.getLoadedEntityList())                
	{                        
		if(((Entity)o).getEntityId() == entityID)                        
		{                                
			System.out.println("Found the entity");                                
			return ((Entity)o);                        
		}                
	}                
	return null;        
}	
}

 

Send the Packet When Needed:

Now all you need to do is to send the packet when you need to.  You could just send it in the onUpdate() method of your entity, but that is a bad idea because it is wasteful -- it sends a packet every tick.  Instead you should send it every time the variables you are tracking change.  Personally it can be tricky to remember to send a packet every time, so I rely on encapsulation -- I only change my variables by using a "setter" method and I put the sending packet inside that sender. 

 

Anyway, it is up to you when you want to send the packets to sync up the client.  The code you need depends on whether you're sending it to just one player (e.g. for their personal GUI) or to all the players (like in my case where I want all the players to see the entity animations at the same time).  Here are some examples of the code you can use to send the packet:

   // method to send sync packet from server to client, should send whenever a custom field is set (from setter method)
// send sync packet to client, if on server side
public void sendSyncPacket()
{
	if (!this.worldObj.isRemote)
	{
    	try 
    	{
			WildAnimals.channel.sendToAll(PacketWildAnimals.createEntityPacket(this));
		} 
    	catch (IOException e) 
    	{
			e.printStackTrace();
		}
	}
}

 

You should check out the other methods for the channel, because you can sendToServer, etc.

 

Hope this helps.  I know it is annoying that you have to do so much work in order to simply send a couple values to the client, but trust me that you have to do it and you might as well learn.  I won't feel bad if you use a different tutorial to do it, but I'm pretty happy with my implementation so far.

 

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

Posted

YOU ARE AWESOME! Thats the first tutorial I have been able to understand all the other tutorials i looked at were insanely complex and impossible for me to figure out but i think i understand how this works now.

 

I just have one thing i cant figure out and that is the "Create and Register Your Packet Handler Classes" part can you please explain in more detail. i dont use init() methods in my proxys and im not sure how to set them up.

 

My proxys:

common

 

  Reveal hidden contents

 

client

 

  Reveal hidden contents

 

And my main mod class:

 

  Reveal hidden contents

 

 

Thank you for your help!

I am the author of Draconic Evolution

Posted
  On 5/3/2014 at 5:44 PM, Jacky2611 said:

@jabelar this is awesome. Could you please post this tutorial somewhere on the wiki?

 

Thanks.  I've just learned modding recently and finally have confidence to share my understanding on a couple topics.  I'm just figuring out best place to post my tutorials and plan to post a few.  I think this packet handling approach deserves a tutorial, and also my entity modeling has gotten pretty good (check out my snake and elephant entities in this video:

).

 

I don't like posting mis-information though, so want to make sure I thoroughly understand something before deciding to teach others.

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

Posted
  On 5/3/2014 at 9:50 PM, brandon3055 said:

so would you be able to help me out with my proxys? im not sure how you set up your init() methods.

 

When you start your tutorials i would very much like to take a look the way you explained this was awesome.

 

Well, as larsgerrit said for TileEntities they are specifically made for syncing extra data between client and server (for instance they are the intermediary for container GUIs and such.  So you don't need to do my packets for that.  But you still can, and mine are a useful general packet that I use a lot for regular Entities.

 

Anyway, for proxies you can do it a few different ways.  This is one of the tricky points to understand when first modding -- the proxies are related to your run configuration (i.e. in Eclipse).  When you run Minecraft, there is always a server but you may or not have a client running locally.  When you use the Client run configuration, it also includes the server classes.  So this idea of proxy helps organize the case where your run configuration is only server, or also server with client. 

 

The server stuff goes in the "Common" proxy because it is common to both the Client and the Server run configurations.  Then any client-specific stuff goes into a "Client" proxy, but that actually extends "Common" proxy to cover both sides that will execute in the run configuration.

 

I know that is kinda confusing, but keep reading it over until it makes sense.

 

Okay, with that being said I personally organize my proxy like this.  Note I took out a lot of stuff to make so you can just see the organization.

 

CommonProxy:

 

  Reveal hidden contents

 

 

And ClientProxy:

 

  Reveal hidden contents

 

 

Then in my mod's main class the organization is very simple and clean.  Basically you call the proxy's related method as appropriate.  Here's just the event handler stuff (not quite my whole main class):

 

  Reveal hidden contents

 

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

Posted

ok now i understand. If you look at my earlier post that is how ALL of the tutorials i have seen so far have set up the proxys. I have never seen anyone add the FMLPreInit/Init/PostInit events to their proxys. After i get some sleep i will try setting up my proxys like yours and will let you know how i go.

 

I will also have to look into what larsgerrits said and see if i can figure that out. 

I am the author of Draconic Evolution

Posted

Ok im calling this one solved! I ended up going with larsgerrits suggestion because it was simple and its designed for this exact application although it did take a bit to figure out because it seems to have changed a little in 1.7.2 For anyone else having the same problem.

@Override
public Packet getDescriptionPacket() {
//Debug
System.out.println("[DEBUG]:Server sent tile sync packet");

NBTTagCompound tagCompound = new NBTTagCompound();
this.writeToNBT(tagCompound);
return new S35PacketUpdateTileEntity(this.xCoord, this.yCoord, this.zCoord, 1, tagCompound);
}

@Override
public void onDataPacket(NetworkManager net, S35PacketUpdateTileEntity pkt) {
//Debug
System.out.println("[DEBUG]:Client recived tile sync packet");

readFromNBT(pkt.func_148857_g());
worldObj.markBlockForUpdate(xCoord, yCoord, zCoord);
}

@jabelar I will still be using your packet handler because the next thing on my list is adding buttons to the gui which requires a packet handler. If i have any more problems figuring it out i will pm you (if that's ok with you?) but i think i have it figured out.

 

I am the author of Draconic Evolution

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.