Jump to content

Recommended Posts

Posted

Hi,

 

I have a problem using tileEntities to represent the inventory of a creature.

 

Objective

The objective is to have a creature (like a robot) which will be controlled by an AI agent and which has an inventory. To do so, i tried to use tileEntities used generally as containers for chests. My entity thus have a reference to an instance of my custom class which extends tileEntity and implements IInventory.

The funniest part is i want my entity to display it's inventory on interaction. Thus i created a GUI corresponding to the inventory.

The problem

The inventory works fine and is correctly used by the entity. The GUI also displays correctly when the player uses a right click on the entity. But when a player try to put something in the inventory of the entity from it's own inventory, the quantities are doubled. And if it tries to remove from the entity inventory to it's own, the items disappear !

 

Broken solution idea

I found out that the inventory on the client side is not correctly updated and synchronized with the server, probably causing this issue. But a sub-problem is that my tileEntity, as it's linked to a moving entity, don't have a defined position. So for now my GUI find back the corresponding tileEntity using the given x as the entity id (recorded in static structure for other purpose anyway) and don't care about y and z. This actually work but is not really clean. Moreover if i use packets, this system also use tileEntities coordinates to find the correct one in the world and i think i cannot use the same trick there as nothing prevents that another tileEntity is placed at (X,0,0).

 

My code for now :

https://gist.github.com/anonymous/045bea9e67a455cb2f3e

 

 

Handler :

public class GuiHandler implements IGuiHandler
{

    /**
     * Return the requested GUI on server side.
     * Always null as no server GUI exists.
     */
    @Override
    public Object getServerGuiElement(int id, EntityPlayer player, World world, int x, int y, int z)
    {
        switch(id)
        {
            case Robotica.GUI.RobotInventory:
                // NB : as the coordinates are given by calling openGUI() the x is used for entity id ! (awful i know)
                return getRobotInventoryTileEntity(player, x);
                
            default:
                return null;
        }
    }

    private Object getRobotInventoryTileEntity(EntityPlayer player, int id)
    {
        MechaEntity e = MechaEntity.getMecha(id);
        
        if(!(e instanceof RobotEntity))
            return null;
        
        if( new Coords(player).distanceTo((RobotEntity) e) > RobotEntity.GUIRANGE)
            return null;
        
        return new RobotInventoryContainer(player.inventory, ((RobotEntity) e).inventory);
    }

    /**
     * Return the requested GUI on client side.
     * The corresponding Id's can be found in a subclass of Robotica : {@link PLCmods.robotica.Robotica.GUI}.
     */
    @Override
    public Object getClientGuiElement(int id, EntityPlayer player, World world, int x, int y, int z)
    {
        switch(id)
        {
            case Robotica.GUI.ControlPanel:
                return new ControlPanelGUI();
                
            case Robotica.GUI.RobotInfo:
                return new InfoFrameGUI();
                    
            case Robotica.GUI.RobotInventory:
                return getRobotInventoryGui(player, x);
                
            default:
                return null;
        }
    }
    
  private Object getRobotInventoryGui(EntityPlayer player, int id)
  {
      MechaEntity e = MechaEntity.getMecha(id);
      
      if(!(e instanceof RobotEntity))
          return null;
      
      if( new Coords(player).distanceTo((RobotEntity) e) > RobotEntity.GUIRANGE)
          return null;
      
      return new RobotInventoryGui(player, (RobotEntity) e);
  }
}

GUI :

public class RobotInventoryGui extends GuiContainer
{
    public static final ResourceLocation texture = new ResourceLocation(Robotica.SID, "textures/gui/robotinv.png");

    public RobotInventoryGui(EntityPlayer player, RobotEntity robot)
    {
        super(new RobotInventoryContainer(player.inventory, robot.inventory));
        xSize = 175;
        ySize = 165;
    }

    @Override
    protected void drawGuiContainerBackgroundLayer(float f, int i, int j)
    {
        GL11.glColor4f(1F, 1F, 1F, 1F);
        FMLClientHandler.instance().getClient().renderEngine.func_110577_a(texture);
        
        int x = (this.width - this.xSize) / 2;
        int y = (this.height - this.ySize) / 2;
        this.drawTexturedModalRect(x, y, 0, 0, this.xSize, this.ySize);
    }
}

 

TileEntity :

public class RobotInventory extends TileEntity implements IInventory
{
    public static final int GRAB_RANGE = 3;

    protected RobotEntity   entity;
    protected ItemStack[]   inv;

    public RobotInventory(RobotEntity robotEntity)
    {
        entity = robotEntity;
        inv = new ItemStack[24];
    }

    @Override
    public int getSizeInventory()
    {
        return inv.length;
    }

    @Override
    public ItemStack getStackInSlot(int i)
    {
        return inv[i];
    }

    @Override
    public ItemStack decrStackSize(int slotId, int quantity)
    {
        if (this.inv[slotId] != null)
        {
            ItemStack itemstack;

            if (this.inv[slotId].stackSize <= quantity)
            {
                itemstack = this.inv[slotId];
                this.inv[slotId] = null;
                this.onInventoryChanged();
                return itemstack;
            }
            else
            {
                itemstack = this.inv[slotId].splitStack(quantity);

                if (this.inv[slotId].stackSize == 0)
                {
                    this.inv[slotId] = null;
                }

                this.onInventoryChanged();
                return itemstack;
            }
        }
        else
        {
            return null;
        }
    }

    @Override
    public ItemStack getStackInSlotOnClosing(int slotId)
    {
        if (this.inv[slotId] != null)
        {
            ItemStack itemstack = this.inv[slotId];
            this.inv[slotId] = null;
            return itemstack;
        }
        else
        {
            return null;
        }
    }

    @Override
    public void setInventorySlotContents(int slotId, ItemStack stack)
    {
        System.out.println("Set slot content of " + slotId);

        this.inv[slotId] = stack;

        if (stack != null && stack.stackSize > this.getInventoryStackLimit())
        {
            stack.stackSize = this.getInventoryStackLimit();
        }

        this.onInventoryChanged();
    }

    @Override
    public String getInvName()
    {
        return "Robot inventory";
    }

    @Override
    public boolean isInvNameLocalized()
    {
        return true;
    }

    @Override
    public int getInventoryStackLimit()
    {
        return 64;
    }

    @Override
    public boolean isUseableByPlayer(EntityPlayer player)
    {
        return player.getDistanceSq(entity.posX, entity.posY, entity.posZ) < 25;
    }

    @Override
    public void openChest()
    {
    }

    @Override
    public void closeChest()
    {
    }

    @Override
    public boolean isItemValidForSlot(int i, ItemStack itemstack)
    {
        return true;// (i < 20 || i > 23);
    }
}

 

Container :

public class RobotInventoryContainer extends Container
{
    private RobotInventory inventory;

    public RobotInventoryContainer(InventoryPlayer playerinv, RobotInventory robotinv)
    {
        this.inventory = robotinv;
        
        for(int i=0;i<4;i++)
            for(int j=0;j<5;j++)
                addSlotToContainer(new Slot(robotinv, i * 5 + j, 82 + j * 18, 6 + i * 18));
        
        for(int i=0;i<4;i++)
            addSlotToContainer(new Slot(robotinv, 20 + i, 6, 6 + i * 18));
        
        for(int i=0;i<3;i++)
            for(int j=0;j<9;j++)
                addSlotToContainer(new Slot(playerinv, 9 + i * 9 + j, 8 + j * 18, 84 + i * 18));
        
        for(int j=0;j<9;j++)
            addSlotToContainer(new Slot(playerinv, j, 8 + j * 18, 142));
    }
    
    public ItemStack transferStackInSlot(EntityPlayer player, int n)
    {
        System.out.println("Transfert in slot " + n);
        
        ItemStack stack = null;
        Slot slotObject = (Slot)this.inventorySlots.get(n);
    
        if (slotObject != null && slotObject.getHasStack())
        {
            ItemStack stack1 = slotObject.getStack();
            stack = stack1.copy();
    
            if(n < 24)
            {
                if(!this.mergeItemStack(stack1,24,59,false)) // (! ((RobotInventory) slotObject.inventory).pickUpItem(itemstack))
                {
                    return null;
                }
            }
            else if (!this.mergeItemStack(stack1, 0, 20, false))
            {
                return null;
            }
            
            if(stack1.stackSize == 0)
            {
                slotObject.putStack(null);
            }
            else
            {
                slotObject.onSlotChanged();
            }
        }
    
        return stack;
    }
    
    @Override
    public boolean canInteractWith(EntityPlayer entityplayer)
    {
        return inventory.isUseableByPlayer(entityplayer);
    }
}

 

 

 

So, if someone has a clever idea, either to solve the GUI interaction, solve synchronisation or to use another mechanism than tileEntity which would be more appropriate, please let me know.

I tried to be concise but if it's not clear don't hesitate to ask more info.

 

Thanks.

 

NB: Sorry if there is a lot of mistakes, English isn't my first language.

Posted

You don't need a tile-entity at all.  In fact, that is the wrong way to handle this completely.  Tile entities are for blocks.

 

For an Entity with an inventory, you need only make your entity implement IInventory (the same way that you would make a tile-entity implement IInventory).

 

After you get all the inventory methods setup properly in the entity, you need a way to reference the entity to open the proper inventory.  When calling to open a GUI, forge gives you three int params to use, x, y, and z.  Use one/two of these to send the entity-id of the entity that has its inventory opened.  Retrieve the proper entity on the other side by calling world.getEntityByID(int id). 

 

(In the following example entity, I have bridged the inventory functions to an internal inventory object instead of implementing the inventory directly in the entity)

 

Example entity-class

public class VehicleBase extends Entity implements IInventory
{
//missing inventory field, as it is a custom class -- use your own implementation of IInventory
@Override
public int getSizeInventory()
  {
  return inventory.getSizeInventory();
  }

@Override
public ItemStack getStackInSlot(int i)
  {
  return inventory.getStackInSlot(i);
  }

@Override
public ItemStack decrStackSize(int i, int j)
  {
  return inventory.decrStackSize(i, j);
  }

@Override
public ItemStack getStackInSlotOnClosing(int i)
  {
  return inventory.getStackInSlotOnClosing(i);
  }

@Override
public void setInventorySlotContents(int i, ItemStack itemstack)
  {
  inventory.setInventorySlotContents(i, itemstack);
  }

@Override
public String getInvName()
  {
  return inventory.getInvName();
  }

@Override
public boolean isInvNameLocalized()
  {
  return inventory.isInvNameLocalized();
  }

@Override
public int getInventoryStackLimit()
  {
  return inventory.getInventoryStackLimit();
  }

@Override
public void onInventoryChanged()
  {
  inventory.onInventoryChanged();
  }

@Override
public boolean isUseableByPlayer(EntityPlayer entityplayer)
  {
  return inventory.isUseableByPlayer(entityplayer);
  }

@Override
public void openChest()
  {
  
  }

@Override
public void closeChest()
  {
  
  }

@Override
public boolean isItemValidForSlot(int i, ItemStack itemstack)
  {
  return inventory.isItemValidForSlot(i, itemstack);
  }
}

 

Server-side gui handler (note how I retrieve the entity by entity ID from the  (x) parameter)

@Override
public Object getServerGuiElement(int ID, EntityPlayer player, World world, int x, int y, int z)
  {
  VehicleBase vehicle; 
  TileEntity te;
  switch(ID)
  {
  case VEHICLE_DEBUG:
  vehicle = (VehicleBase)world.getEntityByID(x);
  if(vehicle!=null)
    {
    return new ContainerVehicle(player, vehicle, vehicle);
    }
  return null;
  
  case PERFORMANCE:
  return new ContainerDebugInfo(player);  
  
  } 
  return null;
  }

 

Client-side gui handler


@Override
public Object getClientGuiElement(int ID, EntityPlayer player, World world, int x, int y, int z)
  {  
  VehicleBase vehicle;
  switch(ID)
  {  
  case VEHICLE_DEBUG:
  vehicle = (VehicleBase)world.getEntityByID(x);
  if(vehicle!=null)
    {
    return new GuiVehicleInventory(new ContainerVehicle(player, vehicle, vehicle));
    }
  return null;//TODO make/set gui & container..
    
  } 
  return null;
  }

 

Code that is called to open the GUI (from right-click on entity)(note entityID sent as the 'x' coordinate)


FMLNetworkHandler.openGui(player, AWCore.instance, GUIID, world, entityID, 0, 0);

 

Posted

Hum ... yeah now i double check nothing really needs a TileEntity object to work properly. I adapted this from a tutorial doing it for a block so this might be why i was so focused on the tileEntity. And the trick to use x as id is already what i use for now.

 

But anyway I still have the same issues, the problems on interaction with the GUI are still there. How can i solve this ? If it's linked to de-synchronization, are they methods to make the entity synchronize with it's corresponding entity on the client side ? I used datawatchers before for other variables like statistics about the entity, but i don't know if it's the good way to synchronize the 24 itemstacks of the inventory.

Posted

If done properly, all synchronization is done automatically by the Container.  If all you are doing is displaying inventory slots / allowing players to move items around in them, there should be nothing extra that you need to do beside setup your container correctly.

 

Also, when you call openGUI, make sure you are calling that from the SERVER side, or it will only open the container on the client...and then you get desynch.  So on your entity-interact method, have it check for if(!worldObj.isRemote){//call FML.openGUI method}

 

 

I'll browse through your code to see if I can spot any problems, but at first glance, your container/etc _should_ be setup properly.

 

Are you manipulating the entities inventory from anywhere else, or accidentally changing things client-side?  (essentially, the inventory only exists server-side...the only time the client has a copy is when the GUI is open...so trying to manipulate the inventory from client side from outside of the container/gui won't work very well) 

Sorry...just trying to list the problems/solutions that I encountered when first working with this stuff...I know it took me quite a while to get my first entity-based inventory working properly.

 

I would also try and eliminate whatever mapping you were using for entity-IDs.  I tried a similar system originally, and found it to be very error prone.  (first of all, the entity-id's are not persistent...and second, they sometimes don't match, especially when an entity is first loaded from disk).  The best way I found was just to use the information directly from the entity when needed, and then retrieve the entity from world.getEntityByID.  (Just mentioning this, as I had some desynch issues with my first attempts at gui/inventory because I was retreiving the wrong entity on either the client or server side....)

 

 

 

 

Posted

This might be the problem. As my entity is managed by an AI, it destroy blocks and collects items from it's own initiative. It's a player-like entity. So in this case, the GUI of the client is not open and i suppose this leads to desynchronization. All of this happens at server side. So i suppose the client isn't aware of these changes.

 

EDIT : Finally it's not ... If the entity collects items and then the player open the GUI, the items are correctly displayed. Only the interractions are wrong.

 

What do you mean by the inventory only exists server-side ? Because as the inventory is part of my entity it's also existing at client-side (meaning the object exists), but is just "incorrect". But maybe a additionnal instance is created on client side when the GUI is oppened. Is that what you mean ?

 

The GUI opening is asked by the server only. Concerning the mapping, i use my own Id's in my own structure managed by the server, not the default one assigned to entities. So i think there should be no problems concerning the ids. Moreover the id is a field stored using the NBT functions so it should never change. I don't really have other ideas as i need to be able to find back the entities in the world from a main controller.

 

NB : to be more precise : The inventory can change while the GUI is close BUT the entity don't move and don't change it's inventory on it's own if a GUI is oppened. In this case only the player who oppened it is able to change the content.

Posted

Can you show me the code you use to make the container work ? I tried to compare to some tutorials i found to fix mine but they don't all agree with each other and it still didn't work.

Posted

You could look at something like EntityHorse class. It uses a method func_110226_cD() that sets up the chest and datawatcher for inventory access. It uses the class AnimalChest - something similar might be useful. Like maybe NpcChest with similar code of your own.

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.