Jump to content

Particles stop spawning! Server/Client desynch?


Frepo

Recommended Posts

So I have my BlockCampfire, together with it's TileEntityCampfire. Which is basically a furnace. But I have added a new heat parameter to the tileentity.

As the vanilla furnace does, my campfire switches between campfireIdle and campfireBurning depending if there is fuel burning inside, with the exception that the campire should remain in the burning state as long as there is any heat left (so it emits light during dark creepy nights). And that works fine.

 

Now I only want it to generate the flame & smoke particles when something (fuel) is burning (furnaceBurnTime > 0). Here is where my problem emerges.

As soon as the burn time is up (the coal lump is used up) it stops spawning the flame particles, EVEN if I have more coal that keeps the campfire going. If I right-click and opens up the GUI it starts spawning again, only to stop again when the burn time is up.

 

The campfire works perfectly (burn, heat, and cook timers, as well as the counters). It's just the damn particles that stops spawning when the currently burning fuel source is consumed. It must be something with the server and the client that goes out of synch or something

 

In BlockCampfire I have moved the spawn particles code to the TileEntityCampfire... (for some reason that I can't remember)

    @SideOnly(Side.CLIENT)
    public void randomDisplayTick(World par1World, int par2, int par3, int par4, Random par5Random)
    {
    	TileEntityCampfire tileentity = (TileEntityCampfire)par1World.getBlockTileEntity(par2, par3, par4);
    	
    	if (tileentity != null && tileentity instanceof TileEntityCampfire)
        {
            tileentity.validate();
            tileentity.generateCampfireFlames(par1World, par2, par3, par4, par5Random);
        }
    }

 

Here are the relevant methods from the TileEntityCampfire class

    public boolean isBurning()
    {
        return this.furnaceBurnTime > 0;
    }

    @SideOnly(Side.CLIENT)
    /** Generate flame animation while fuel is still burning */
    public void generateCampfireFlames(World par1World, int par2, int par3, int par4, Random par5Random)
    {
    	if (this.isBurning())
        {
            float f = (float)par2 + 0.5F;
            float f1 = (float)par3 + 0.0F + par5Random.nextFloat() * 4.0F / 16.0F;
            float f2 = (float)par4 + 0.5F;
            float f3 = par5Random.nextFloat() * 0.6F - 0.3F;
            float f4 = par5Random.nextFloat() * 0.6F - 0.3F;

            par1World.spawnParticle("smoke", (double)(f + f3), (double)f1, (double)(f2 + f4), 0.0D, 0.0D, 0.0D);
            par1World.spawnParticle("flame", (double)(f + f3), (double)f1, (double)(f2 + f4), 0.0D, 0.0D, 0.0D); 
        }
    }

 

    /**
     * Allows the entity to update its state. Overridden in most subclasses, e.g. the mob spawner uses this to count
     * ticks and creates a new spawn inside its implementation.
     */
    public void updateEntity()
    {
    	boolean flag1 = false;
    	boolean flag2 = this.currentHeat > 0;
        
        if (this.furnaceBurnTime > 0){
            --this.furnaceBurnTime;
            if(this.currentHeat > this.maxFuelHeat)
            	this.decreaseHeat();
            else
            	this.increaseHeat();
        }
        else{
        	this.decreaseHeat();
        }
        
        if (!this.worldObj.isRemote)
        { // *** isRemote bracket	
        	
        	if (this.furnaceBurnTime == 0 && this.canSmelt())
            {
        		this.maxFuelHeat = getMaxFuelHeat(this.furnaceItemStacks[1]);
        		this.currentItemBurnTime = this.furnaceBurnTime = getItemBurnTime(this.furnaceItemStacks[1]);

                if (this.furnaceBurnTime > 0)
                {
                	flag1 = true;

                	if (this.furnaceItemStacks[1] != null)
                    {
                        --this.furnaceItemStacks[1].stackSize;

                        if (this.furnaceItemStacks[1].stackSize == 0)
                        {
                            this.furnaceItemStacks[1] = this.furnaceItemStacks[1].getItem().getContainerItemStack(furnaceItemStacks[1]);
                        }
                    }
                }
            }
        	
            if (this.canSmelt() && this.currentHeat >= this.getHeatNeeded()) //this.isBurning() && 
            {
                ++this.furnaceCookTime;

                if (this.furnaceCookTime == this.cookSpeed)
                {
                    this.furnaceCookTime = 0;
                    this.smeltItem();
                    flag1 = true;
                }
            }
            else
            {
                this.furnaceCookTime = 0;
            }

            if (flag2 != this.currentHeat > 0)
            {
                flag1 = true;
                BlockCampfire.updateFurnaceBlockState(this.currentHeat > 0, this.worldObj, this.xCoord, this.yCoord, this.zCoord);
            }
        } // *** isRemote ending bracket
    	
        if (flag1)
        {
            this.onInventoryChanged();
        }
        
    }

 

why WHY WHYYYYY does the particles stop spawning!?

 

/Thanks in advance for any help

Link to comment
Share on other sites

Hi

 

A suggestion - try adding System.out.println("{log msg here}"); at key points in your code, eg

    @SideOnly(Side.CLIENT)

    public void randomDisplayTick(World par1World, int par2, int par3, int par4, Random par5Random)

    {

    TileEntityCampfire tileentity = (TileEntityCampfire)par1World.getBlockTileEntity(par2, par3, par4);

System.out.println("randomDisplayTick");

   

    if (tileentity != null && tileentity instanceof TileEntityCampfire)

        {

System.out.println("tileentity correct");

 

            tileentity.validate();

            tileentity.generateCampfireFlames(par1World, par2, par3, par4, par5Random);

        }

    }

 

That should quickly help you narrow down which part is not working as you expect - eg the randomDisplayTicks stop, your entity disappears, isBurning() becomes false, etc.

 

-TGG

Link to comment
Share on other sites

Thanks for the tip! I'll do some research with that in mind.

 

Are you familiar with the isRemote-check? I know what it does, and why it is used. I'm just not sure where to place it.

 

Have I misplaced it in updateEntity? I've tried to place it in several locations, ending up with different results. Heat and cook timers start bugging out, the progress bars stops and stuff like that.

 

 

Link to comment
Share on other sites

Hi

 

There's a bit more background about the Client-Server division here that might help

http://greyminecraftcoder.blogspot.com/2013/10/the-most-important-minecraft-classes.html

http://greyminecraftcoder.blogspot.com/2013/10/client-server-communication-using.html

http://greyminecraftcoder.blogspot.com.au/2013/10/client-side-class-linkage-map.html

http://greyminecraftcoder.blogspot.com/2013/10/server-side-class-linkage-map.html

 

To be honest I also find it difficult to be sure when I have to do something on one side only.  Some are obvious (such as rendering) but often it's not.    I use vanilla code as a guideline, i.e. I find a block or item or whatever in the vanilla code that does something similar to what I want, and see whether they are bothering with isRemote.  If I'm not sure about a particular method, I'll search for all usages of it in the code and look to see if they're called exclusively from one side or from both.

 

-TGG

 

 

Link to comment
Share on other sites

Haha these charts make me even more confused :D

Updating variables in TileEntities (such as cookTim, burnTime etc) should be done server-side right?

Another thing, what happens in all functions that don't have a .isRemote statement? Is that code executed on both server and client?

Link to comment
Share on other sites

Hi

 

The general rules are-

The server holds the master copy of the world, and things which affect the state of the world (blocks, changes to entities, movement, etc) are nearly always calculated on the server and the results sent to any clients which are affected (nearby).

Things like rendering are usually done on the client only, likewise user interactions.

 

In some cases, the client and the server do the same calculations in parallel to help make the game more responsive, but if there is a clash down the track (eg the position of an entity) then the server takes priority.

 

Some classes are only ever called on the server side (eg the ones in that server-side chart)

Some classes are only ever called on the client side (eg the ones in that client-side chart) - and often the methods are marked as @SideOnly(Side.CLIENT)

 

Some are called in both- if you see a vanilla method with .isRemote() in it, you can be reasonably confident that both sides might call it (otherwise there would be no point in checking it).

 

If the method you're looking at isn't one of the classes in the charts above, and none of the methods have .isRemote() in it, then you usually can't be sure which sides (or both) call it.

 

Perhaps other folks on the forum have a more reliable method of telling.  If I had more technical knowledge I'd run a coverage checker and find out, maybe I'll do that one of these days...

 

-TGG

 

 

 

 

Link to comment
Share on other sites

Thanks for the clarification Ghost. I got the particles to spawn as long as the heat is > 0. And render a fire animation inside the campfire as long as something is burning.

But now the currentItemBurnTime is out of sync :/

The actual time each item is burning seems to be fine... it's just the progressbar is behaving strange. AND, I get not fire animation when I first start cooking something, I must close the GUI and open it again to get the fire to render. My oh my... this is so frustrating!

 

BlockCampfire

 

 

    @SideOnly(Side.CLIENT)
    /** A randomly called display update to be able to add particles or other items for display */
    public void randomDisplayTick(World par1World, int par2, int par3, int par4, Random par5Random)
    {
    	TileEntityCampfire tileentity = (TileEntityCampfire)par1World.getBlockTileEntity(par2, par3, par4);
    	
    	if (tileentity != null && tileentity instanceof TileEntityCampfire)
        {
    		if (tileentity.isBurning() || this.isActive)
            {
                float f = (float)par2 + 0.5F;
                float f1 = (float)par3 + 0.0F + par5Random.nextFloat() * 4.0F / 16.0F;
                float f2 = (float)par4 + 0.5F;
                float f3 = par5Random.nextFloat() * 0.6F - 0.3F;
                float f4 = par5Random.nextFloat() * 0.6F - 0.3F;

                par1World.spawnParticle("smoke", (double)(f + f3), (double)f1, (double)(f2 + f4), 0.0D, 0.0D, 0.0D);
                par1World.spawnParticle("flame", (double)(f + f3), (double)f1, (double)(f2 + f4), 0.0D, 0.0D, 0.0D); 
                
                if (tileentity.isBurning() && par5Random.nextInt(10) == 0)
                {
                	f = (float)par2 + 0.5F;  f1 = (float)par3 + 0.0F + par5Random.nextFloat() * 4.0F / 16.0F;
                    f2 = (float)par4 + 0.5F; f3 = par5Random.nextFloat() * 0.6F - 0.2F;
                    f4 = par5Random.nextFloat() * 0.6F - 0.3F;
                    par1World.spawnParticle("largesmoke", (double)(f + f3), (double)f1, (double)(f2 + f4), 0.0D, 0.0D, 0.0D);
                   
                	if(par5Random.nextInt(2) == 0) par1World.playSound((double)((float)par2 + 0.5F), (double)((float)par3 + 0.5F), (double)((float)par4 + 0.5F), "fire.fire", 1.0F + par5Random.nextFloat(), par5Random.nextFloat() * 0.7F + 0.3F, false);
                }
            }
    		
    		tileentity.validate();
        }
    }

 

 

 

TileEntityCampfire

 

 

    public void updateEntity()
    {
    	boolean flag1 = false;
    	boolean flag2 = this.currentHeat > 0;
    	boolean flag3 = false;
        
    	this.fireFrame++;
    	if(this.fireFrame > 31) {this.fireFrame=0;}
    	
        if (this.furnaceBurnTime > 0){
            --this.furnaceBurnTime;
            if(this.currentHeat > this.maxFuelHeat)
            	this.decreaseHeat();
            else
            	this.increaseHeat();
        }
        else{
        	this.decreaseHeat();
        }
        
        if (this.furnaceBurnTime == 0 && this.canSmelt())
        {
        	this.maxFuelHeat = getMaxFuelHeat(this.furnaceItemStacks[1]);
    		this.currentItemBurnTime = this.furnaceBurnTime = getItemBurnTime(this.furnaceItemStacks[1]);
    		flag3=true;
        }
        
        if (!this.worldObj.isRemote)
        { // *** isRemote bracket	
        	
        	if (flag3 && this.canSmelt())
            {
                if(flag3)
                {

                	flag1 = true;

                	if (this.furnaceItemStacks[1] != null)
                    {
                        --this.furnaceItemStacks[1].stackSize;

                        if (this.furnaceItemStacks[1].stackSize == 0)
                        {
                            this.furnaceItemStacks[1] = this.furnaceItemStacks[1].getItem().getContainerItemStack(furnaceItemStacks[1]);
                        }
                    }
                }
            }
        	
            if (this.canSmelt() && this.currentHeat >= this.getHeatNeeded())
            {
                ++this.furnaceCookTime;

                if (this.furnaceCookTime == this.cookSpeed)
                {
                    this.furnaceCookTime = 0;
                    this.smeltItem();
                    flag1 = true;
                }
            }
            else
            {
                this.furnaceCookTime = 0;
            }

            if (flag2 != this.currentHeat > 0)
            {
                flag1 = true;
                BlockCampfire.updateFurnaceBlockState(this.currentHeat > 0, this.worldObj, this.xCoord, this.yCoord, this.zCoord);
            }
        } // *** isRemote ending bracket
    	
        if (flag1)
        {
            this.onInventoryChanged();
        }
      
    }

 

 

 

 

 

    /**
     * Returns true if the campfire is currently burning
     */
    public boolean isBurning()
    {
        return this.furnaceBurnTime > 0;
    }

 

 

 

 

 

    @SideOnly(Side.CLIENT)
    /**
     * Returns an integer between 0 and the passed value representing how much burn time is left on the current fuel
     * item, where 0 means that the item is exhausted and the passed value means that the item is fresh
     */
    public int getBurnTimeRemainingScaled(int par1)
    {
        if (this.currentItemBurnTime == 0)
        {
            this.currentItemBurnTime = this.cookSpeed; // cookSpeed = 320 always for campfires
        }

        return this.furnaceBurnTime * par1 / this.currentItemBurnTime;
    }

 

 

 

TileEntityCampfireRenderer

 

 

    @SideOnly(side.CLIENT)
    public void renderTileEntityCampfireAt(TileEntityCampfire entity, double x, double y, double z, float f)
    {
    	Tessellator tessellator = Tessellator.instance;
    	double ff;
    	
    	// Render campfire model
    	GL11.glPushMatrix();
        GL11.glTranslatef((float)x + 0.5F, (float)y + 1.5F, (float)z + 0.5F);
        GL11.glRotatef(180, 0.0F, 0.0F, 1.0F);
    
        this.func_110628_a(texCampfire); // bind texture
        
        GL11.glPushMatrix();
        	this.model.renderModel(0.0625F);
        GL11.glPopMatrix();
        
        GL11.glColor4f(1.0F, 1.0F, 1.0F, 1.0F);
        GL11.glPopMatrix();
        
        // Render fire animation
        if(entity.isBurning()){
        GL11.glPushMatrix();
        	
        	ff = (double)entity.getFireFrame();
	        this.func_110628_a(texFireFlame); // bind texture
	        
	        GL11.glPushMatrix();
		        tessellator.startDrawingQuads();
		    	
		        tessellator.addVertexWithUV((double)x+0.2D, (double)y     , (double)z+0.2F, 0 , (ff+1D)*(1D/32D));
		        tessellator.addVertexWithUV((double)x+0.2D, (double)y+0.7D, (double)z+0.2F, 0 , ff*(1D/32D));
		        tessellator.addVertexWithUV((double)x+0.8D, (double)y+0.7D, (double)z+0.8F, 1D, ff*(1D/32D));
		        tessellator.addVertexWithUV((double)x+0.8D, (double)y     , (double)z+0.8F, 1D, (ff+1D)*(1D/32D));
		        
		        tessellator.addVertexWithUV((double)x+0.8D, (double)y     , (double)z+0.8F, 0 , (ff+1D)*(1D/32D));
		        tessellator.addVertexWithUV((double)x+0.8D, (double)y+0.7D, (double)z+0.8F, 0 , ff*(1D/32D));
		        tessellator.addVertexWithUV((double)x+0.2D, (double)y+0.7D, (double)z+0.2F, 1D, ff*(1D/32D));
		        tessellator.addVertexWithUV((double)x+0.2D, (double)y     , (double)z+0.2F, 1D, (ff+1D)*(1D/32D));
		        
		        tessellator.addVertexWithUV((double)x+0.2D, (double)y     , (double)z+0.8F, 0 , (ff+1D)*(1D/32D));
		        tessellator.addVertexWithUV((double)x+0.2D, (double)y+0.7D, (double)z+0.8F, 0 , ff*(1D/32D));
		        tessellator.addVertexWithUV((double)x+0.8D, (double)y+0.7D, (double)z+0.2F, 1D, ff*(1D/32D));
		        tessellator.addVertexWithUV((double)x+0.8D, (double)y     , (double)z+0.2F, 1D, (ff+1D)*(1D/32D));
		        
		        tessellator.addVertexWithUV((double)x+0.8D, (double)y     , (double)z+0.2F, 0 , (ff+1D)*(1D/32D));
		        tessellator.addVertexWithUV((double)x+0.8D, (double)y+0.7D, (double)z+0.2F, 0 , ff*(1D/32D));
		        tessellator.addVertexWithUV((double)x+0.2D, (double)y+0.7D, (double)z+0.8F, 1D, ff*(1D/32D));
		        tessellator.addVertexWithUV((double)x+0.2D, (double)y     , (double)z+0.8F, 1D, (ff+1D)*(1D/32D));
		        			        
		        tessellator.draw();
	        GL11.glPopMatrix();
	        
	        GL11.glColor4f(1.0F, 1.0F, 1.0F, 1.0F);
        GL11.glPopMatrix();
        }
        
    }

 

 

Link to comment
Share on other sites

Hi

 

yeah it's pretty bad when it just won't work and there aren't any clues why not.  I can't see anything obvious wrong with your code (but then - I've never made a custom furnace so that's not saying much..!)

 

So your campfire is pretty much exactly like the vanilla furnace except it looks different, yeah?  And when you fixed the particles problem, the progressbar counter messed up?

What change did you make that stopped the progressbar from working properly?

 

From memory the vanilla furnace works through a GUIHandler of some sort and there is some fairly complicated synchronisation going on between the client and the server.  Also, the furnace changes its blockID depending on whether it is burning or not.  So if you get really stuck, I would suggest starting by making an exact copy of the vanilla furnace blocks, tileentity, Container, and GUI classes, and tweaking them one bit at a time to add the various effects you want.

 

Sorry I can't be of more help, I haven't fooled with the furnace at all so this is all guesswork for me

 

-TGG

Link to comment
Share on other sites

Although I know there are build-in methods to synch the server with the client, The following solution uses the TileEntity packet. It gets your NBT data of the server, TileEntity#writeToNBT() (assuming you have that implemented), wraps that in a packet, and sends that to the clients which will handle that via TileEntity#readFromNBT(). So any variable you want to have synched with the client you can put in your NBT methods (fortunately these are often the same as the variables you want to save to disk with NBT).

 

Put this in your TileEntity class:

@Override
    public Packet getDescriptionPacket(){
        NBTTagCompound tag = new NBTTagCompound();
        writeToNBT(tag);
        return new Packet132TileEntityData(xCoord, yCoord, zCoord, 0, tag);
    }

@Override
    public void onDataPacket(INetworkManager net, Packet132TileEntityData pkt){
        readFromNBT(pkt.data);
    }

public void sendDescriptionPacket(){
        PacketDispatcher.sendPacketToAllAround(xCoord, yCoord, zCoord, 64D, worldObj.provider.dimensionId, getDescriptionPacket());
    }

 

Now every time you want to send the clients(!) an update, you can invoke sendDescriptionPacket(). Keypoint where you want to do this is when you burn up a new burnable item, i.e. you're setting the burn timer again.

 

Remember that you should only do this when you're sure clients will be desynched, because if you invoke sendDescriptionPacket() unnecessarily often this will affect the network. Also this will send an update packet to every player within 64 blocks of this TE, which is great in this example but may not be in others.

Author of PneumaticCraft, MineChess, Minesweeper Mod and Sokoban Mod. Visit www.minemaarten.com to take a look at them.

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.



×
×
  • Create New...

Important Information

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