Bloc de type four


  • Rédacteurs

    Sommaire

    Introduction

    Nous allons créer un bloc qui prend des items en entrée et qui sort d'autres items en sortie comme à la façon d'un four par exemple.
    Pour cet exemple j'ai choisi un bloc qui prendra 4 items en entrée, 1 item pour alimenter et qui sortira 2 items.
    Je vous montrerez cependant comment personnaliser le code pour obtenir ce que vous voulez mais pour cela il va falloir
    bien comprendre le code donc soyer attentif.

    Pré-requis

    Code

    La classe principale

    On va commencer par déclarer notre bloc, dans ce tutoriel je l'appellerai machineTuto

    public static Block machineTuto;
    

    Pensez à respecter la convention Java, cette variable commence par une minuscule.

    Dans la méthode preInit nous allons instancier notre bloc

    machineTuto = new BlockMachineTuto();
    

    Encore une fois la convention Java préconise que les classes commencent par une majuscule.
    Créez la classe BlockMachineTuto.

    Toujours dans la classe principale mais cette fois dans la méthode init nous allons enregistrer notre TileEntity

    GameRegistry.registerTileEntity(TileEntityMachineTuto.class, "tileentitymachinetuto");
    

    Créez la classe TileEntityMachineTuto.

    Il faut aussi enregistrer le Gui Handler :

    NetworkRegistry.INSTANCE.registerGuiHandler(instance, new GuiHandlerTuto());
    

    Voilà pour la classe principale.

    La classe du bloc

    Allez maintenant dans la classe du bloc et faite-la étendre à BlockContainer

    public class BlockMachineTuto extends BlockContainer
    

    Ajoutez le constructeur

    
    public BlockMachineTuto() {
    super(Material.rock); //Choisissez ce que que vous voulez
    //Autres paramètres
    }
    
    

    On va maintenant déclarer que le bloc possède un TileEntity et dire quel TileEntity créer

    
    @Override
    public boolean hasTileEntity() {
        return true;
    }
    
    @Override
    public TileEntity createNewTileEntity(World world, int metadata)  {
        return new TileEntityMachineTuto();
    }
    
    

    Maintenant occupons-nous de la méthode qui va permettre de drop les items quand on casse le bloc

    
    @Override
    public void breakBlock(World world, BlockPos pos, IBlockState state) {
        TileEntity tile = world.getTileEntity(pos);
    
        if(tile instanceof TileEntityMachineTuto) {
            InventoryHelper.dropInventoryItems(world, pos, (TileEntityMachineTuto)tile);
        }
        super.breakBlock(world, pos, state);
    }
    
    

    Et maintenant la méthode pour ouvrir le gui lorsqu'on fait clique droit sur le bloc

    
    @Override
    public boolean onBlockActivated(World world, BlockPos pos, IBlockState state, EntityPlayer player, EnumFacing side, float hitX, float hitY, float hitZ) {
        if(world.isRemote)
        {
            return true;
        }
        else
        {
            player.openGui(ModTutoriel.instance, 0, world, pos.getX(), pos.getY(), pos.getZ());
            return true;
        }
    }
    
    

    Pour que votre bloc soit rendu comme un bloc normal il va falloir Override la fonction getRenderType

    
    @Override
    public int getRenderType()
    {
        return 3;
    }
    
    

    Voilà pour la classe du bloc

    La classe du TileEntity

    Allez dans la classe du TileEntity (TileEntityMachineTuto), c'est ici qu'une grande partie
    du système va se dérouler il est donc important de comprendre.

    On va déclarer plusieurs variables

    
    private String customName;
    public static final int INPUTS_SLOTS = 4;
    public static final int FUEL_SLOTS = 1;
    public static final int OUTPUT_SLOTS = 2;
    private ItemStack[] contents = new ItemStack[INPUTS_SLOTS + FUEL_SLOTS + OUTPUT_SLOTS];
    private int currentWorkingTime = 0;
    private int totalWorkingTime = 300;
    private int remainingFuelTime = 0;
    
    

    Quelques explications :
    -customName contient le nom custom du bloc si il en a un
    -INPUTS_SLOTS contient le nombre de slots de type input (slots inputs = slots où on va mettre les items pour la recette)
    -FUEL_SLOTS contient le nombre de slots où on va mettre le carburant;
    -OUTPUT_SLOTS contient le nombre de slots d'output (slots output = slots où les items créés vont)
    -contents contient les ItemStack de votre bloc autrement dit tout les slots, c'est ici que sont stockés les items
    -currentWorkingTime contient l'avancement de la recette, il représente le temps passé
    -totalWorkingTime contient le temps nécessaire pour que la recette soit finie
    -remainingFuelTime contient la quantitée de carburant restant

    Maintenant étendons la classe à TileEntity et implémentons IUpdatePlayerListBox ainsi que ISidedInventory

    public class TileEntityMachineTuto extends TileEntity implements IUpdatePlayerListBox, ISidedInventory
    

    N'implémentez pas pas les méthodes, on va le faire à la main.

    Tout d'abord les méthodes pour lire et écrire dans les NBT, dedans on lit, on écrit, rien de sorcier

    
    @Override
    public void readFromNBT(NBTTagCompound compound)
    {
        super.readFromNBT(compound);
        NBTTagList nbttaglist = compound.getTagList("Items", 10);
        this.contents = new ItemStack[this.getSizeInventory()];
    
        for (int i = 0; i < nbttaglist.tagCount(); ++i)
        {
            NBTTagCompound nbttagcompound1 = nbttaglist.getCompoundTagAt(i);
            byte b0 = nbttagcompound1.getByte("Slot");
    
            if (b0 >= 0 && b0 < this.contents.length)
            {
                this.contents[b0] = ItemStack.loadItemStackFromNBT(nbttagcompound1);
            }
        }
    
        this.currentWorkingTime = compound.getShort("CurrentWorkingTime");
        this.totalWorkingTime = compound.getShort("TotalWorkingTime");
        this.remainingFuelTime = compound.getShort("RemainingFuelTime");
    
        if (compound.hasKey("CustomName", 8))
        {
            this.customName = compound.getString("CustomName");
        }
    }
    
    @Override
    public void writeToNBT(NBTTagCompound compound)
    {
        super.writeToNBT(compound);
    
        compound.setShort("CurrentWorkingTime", (short)this.currentWorkingTime);
        compound.setShort("TotalWorkingTime", (short)this.totalWorkingTime);
        compound.setShort("RemainingFuelTime", (short)this.remainingFuelTime);
    
        NBTTagList nbttaglist = new NBTTagList();
    
        for (int i = 0; i < this.contents.length; ++i)
        {
            if (this.contents[i] != null)
            {
                NBTTagCompound nbttagcompound1 = new NBTTagCompound();
                nbttagcompound1.setByte("Slot", (byte)i);
                this.contents[i].writeToNBT(nbttagcompound1);
                nbttaglist.appendTag(nbttagcompound1);
            }
        }
    
        compound.setTag("Items", nbttaglist);
    
        if (this.hasCustomName())
        {
            compound.setString("CustomName", this.customName);
        }
    }
    
    

    Maintenant les méthodes qui gèrent le nom du tile

    
    @Override
    public String getName() {
        return this.hasCustomName() ? this.customName : "tuto.machinetuto";
    }
    
    @Override
    public boolean hasCustomName() {
        return this.customName != null && this.customName.length() > 0;
    }
    
    public void setCustomInventoryName(String name)
    {
        this.customName = name;
    }
    
    @Override
    public IChatComponent getDisplayName() {
        return new ChatComponentText(this.getName());
    }
    
    

    Méthode qui récupère la taille taille de l'inventaire, autrement dit le nombre de slots

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

    Méthodes de manipulation des slots, en mettant la souris sur le nom de la fonction vous pouvez lire la javadoc, c'est de l'anglais facile

    
    @Override
    public ItemStack getStackInSlot(int index) {
        return this.contents[index];
    }
    
    @Override
    public ItemStack decrStackSize(int index, int count) {
        if (this.contents[index] != null)
        {
            ItemStack itemstack;
    
            if (this.contents[index].stackSize <= count)
            {
                itemstack = this.contents[index];
                this.contents[index] = null;
                this.markDirty();
                return itemstack;
            }
            else
            {
                itemstack = this.contents[index].splitStack(count);
    
                if (this.contents[index].stackSize == 0)
                {
                    this.contents[index] = null;
                }
                this.markDirty();
                return itemstack;
            }
        }
        else
        {
            return null;
        }
    }
    
    @Override
    public ItemStack getStackInSlotOnClosing(int index)
    {
        if (this.contents[index] != null)
        {
            ItemStack itemstack = this.contents[index];
            this.contents[index] = null;
            return itemstack;
        }
        else
        {
            return null;
        }
    }
    
    @Override
    public void setInventorySlotContents(int index, ItemStack stack)
    {
        this.contents[index] = stack;
        if(stack != null && stack.stackSize > this.getInventoryStackLimit())
        {
            stack.stackSize = this.getInventoryStackLimit();
        }
    }
    
    

    Fonction qui renvoie la taille maximale d'un stack dans notre inventaire

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

    Peut-on utiliser l'inventaire ? Ça dépend de notre distance avec le bloc

    
    @Override
    public boolean isUseableByPlayer(EntityPlayer player)  {
        return this.worldObj.getTileEntity(this.pos) != this ? false : player.getDistanceSq((double)this.pos.getX() + 0.5D, (double)this.pos.getY() + 0.5D, (double)this.pos.getZ() + 0.5D) <= 64.0D;
    }
    
    

    Fonctions que l'on en vas pas utiliser mais qui sont respectivement appellée lors de
    l'ouverture et la fermeture de l'inventaire

    
    @Override
    public void openInventory(EntityPlayer player) {}
    
    @Override
    public void closeInventory(EntityPlayer player) {}
    
    

    Fonction qui renvoie si l'item mis en paramètre est valide pour le slot mis en paramètre

    
    @Override
    public boolean isItemValidForSlot(int index, ItemStack stack) {
        return index >= INPUTS_SLOTS + FUEL_SLOTS ? false : index >= INPUTS_SLOTS && !this.isFuel(stack) ? false : true;
    }
    
    

    On va créer la fonction qui nous manque juste ici au dessus, c'est isFuel(ItemStack)

    
    private boolean isFuel(ItemStack stack) {
        return stack == null ? false : stack.getItem() == Items.blaze_powder;
    }
    
    

    Notre carburant sera ici la poudre de blaze, ajoutez-y vos carburants.

    Maintenant les fonctions qui permettent de manipuler nos variables privée depuis l'extérieur de la classe

    
    @Override
    public int getField(int id) {
        switch(id)
        {
        case 0:
            return this.currentWorkingTime;
        case 1:
            return this.totalWorkingTime;
        case 2:
            return this.remainingFuelTime;
        default:
            return 0;
        }
    }
    
    @Override
    public void setField(int id, int value) {
        switch(id)
        {
        case 0:
            this.currentWorkingTime = value;
            break;
        case 1:
            this.totalWorkingTime = value;
            break;
        case 2:
            this.remainingFuelTime = value;
        }
    }
    
    @Override
    public int getFieldCount() {
        return 3;
    }
    
    

    Il y a beaucoups de fonctions mais le résultat en vaut la peine.
    Cette fonction permet de vider l'inventaire entièrement

    
    @Override
    public void clear() {
        for(int i = 0; i < this.contents.length; i++)
        {
            this.contents* = null;
        }
    }
    
    

    Nous avons implémenter ISidedInventory, c'est pour pouvoir insérer des items via des hopper
    par exemple, parce que l'automatisation : c'est bien. Ainsi il faut savoir quel côté correspond
    à quel id de slot. J'ai pour cela créé 3 fonctions qui renvoient les ids des 3 types de slots
    définis au début (input, fuel, output).

    
    private int[] getInputSlotsIds() {
        int[] ids = new int[INPUTS_SLOTS];
        for(int i = 0; i < INPUTS_SLOTS; i++)
        {
            ids* = i;
        }
        return ids;
    }
    
    private int[] getFuelSlotsIds() {
        int[] ids = new int[FUEL_SLOTS];
        int k = 0;
        for(int i = INPUTS_SLOTS; i < (INPUTS_SLOTS + FUEL_SLOTS); i++)
        {
            ids[k] = i;
            k++;
        }
        return ids;
    }
    
    private int[] getOutputSlotsIds() {
        int[] ids = new int[OUTPUT_SLOTS];
        int k = 0;
        for(int i = (INPUTS_SLOTS + FUEL_SLOTS); i < (INPUTS_SLOTS + FUEL_SLOTS + OUTPUT_SLOTS); i++)
        {
            ids[k] = i;
            k++;
        }
        return ids;
    }
    
    

    Voilà on peut récupérer les ids, il n'y a plus qu'à renvoyer la bonne liste en
    fonction du côté

    
    @Override
    public int[] getSlotsForFace(EnumFacing side) {
        if(side == EnumFacing.UP) return this.getInputSlotsIds();
        if(side == EnumFacing.DOWN) return this.getOutputSlotsIds();
        return this.getFuelSlotsIds();
    }
    
    

    Le jeu va ensuite demander si il peut insérer tel item dans tel slot, on lui
    répond alors

    
    @Override
    public boolean canInsertItem(int index, ItemStack stack, EnumFacing direction)
    {
        return this.isItemValidForSlot(index, stack);
    }
    
    

    Et le jeu demande (il fait que ça) si il peuxx extraire tel item de tel slot

    
    @Override
    public boolean canExtractItem(int index, ItemStack stack, EnumFacing direction) {
        for(int id : this.getOutputSlotsIds())
        {
            if(id == index) return true;
        }
        return false;
    }
    
    

    Pour la suite je vais avoir besoin de récupérer les ItemStack des slots d'input
    et des slots d'output

    
    private ItemStack[] getInputSlotsContents() {
        ItemStack[] s = new ItemStack[INPUTS_SLOTS];
        for(int i = 0; i < INPUTS_SLOTS; i++)
        {
            s* = this.getStackInSlot(i);
        }
        return s;
    }
    
    private ItemStack[] getOutputSlotsContents() {
        ItemStack[] s = new ItemStack[OUTPUT_SLOTS];
        int k = 0;
        for(int i = (INPUTS_SLOTS + FUEL_SLOTS); i < (INPUTS_SLOTS + FUEL_SLOTS + OUTPUT_SLOTS); i++)
        {
            s[k] = this.getStackInSlot(i);
            k++;
        }
        return s;
    }
    
    

    D'autres informations peuvent être utiles comme :

    • Est ce qu'une recette est en cours ?
    • Est ce qu'un slot d'input est vide ?
    • Est ce que tout les slots d'output sont vide ?
    
    public boolean isWorking() {
        return this.currentWorkingTime > 0;
    }
    
    private boolean hasNullInput() {
        for(int i = 0; i < INPUTS_SLOTS; i++)
        {
            if(this.getStackInSlot(i) == null) return true;
        }
        return false;
    }
    
    private boolean areOutputsNull() {
        for(int i = (INPUTS_SLOTS + FUEL_SLOTS); i < (INPUTS_SLOTS + FUEL_SLOTS + OUTPUT_SLOTS); i++)
        {
            if(this.getStackInSlot(i) != null) return false;
        }
        return true;
    }
    
    

    On a aussi besoin de savoir si on peut combiner deux tableaux ItemStack donnés, pour savoir si
    on peut mettre l'output de la recette dans les slots d'output

    
    private boolean canCombineStacks(ItemStack[] stack1, ItemStack[] stack2) {
        if(stack1.length != stack2.length) return false;
        for(int i = 0; i < stack1.length; i++)
        {
            if(!this.canCombineItems(stack1*, stack2*))
            {
                return false;
            }
        }
        return true;
    }
    
    private boolean canCombineItems(ItemStack item1, ItemStack item2) {
        if (item1 == null) return true;
        if (!item1.isItemEqual(item2)) return false;
        int result = item1.stackSize + item2.stackSize;
        return result <= getInventoryStackLimit() && result <= item1.getMaxStackSize();
    }
    
    

    Deux fonctions pour s'occuper du niveau de fuel me paraît pas trop mal, une qui renvoie si il
    reste du fuel et une autres qui si il n'en reste pas essai de recharger et qui renvoie finalement
    le si oui ou non on est à sec.

    
    private boolean outOfFuel() {
        if(!this.isRemainingFuel()) {
            for(int i = INPUTS_SLOTS; i < (INPUTS_SLOTS + FUEL_SLOTS); i++) {
                if(this.getStackInSlot(i) != null) {
                    int duration = RecipesMachineTuto.instance().getFuelDuration(this.getStackInSlot(i));
                    if(duration > 0) {
                        this.remainingFuelTime = duration;
                        this.decrStackSize(i, 1);
                    }
                }
            }
        }
        this.markDirty();
        return !this.isRemainingFuel();
    }
    
    public boolean isRemainingFuel() {
        return this.remainingFuelTime > 0;
    }
    
    

    On vient d'avoir la première apparition de la classe RecipesMachineTuto, n'attendez pas,
    créez-la et laissez les erreurs comme quoi il connait pas les fonctions on va le faire après.

    Maintenant une fonction pour savoir si on peut lancer la recette

    
    private boolean canWork() {
        if(this.hasNullInput()) {
            return false;
        }
        ItemStack[] itemstacks = RecipesMachineTuto.instance().getRecipeResult(this.getInputSlotsContents());
        if (itemstacks == null) {
            return false;
        }
        if(this.outOfFuel()) return false;
        if (this.areOutputsNull()) return true;
        if (!this.canCombineStacks(this.getOutputSlotsContents(), itemstacks)) return false;
        return true;
    }
    
    

    Et une autres pour appliquer la recette (enlever les items dans les slots d'input et rajouter dans les slots d'output)

    
    private void applyRecipe() {
        ItemStack[] itemstacks = RecipesMachineTuto.instance().getRecipeResult(this.getInputSlotsContents());
        if(this.areOutputsNull()) {
            int k = 0;
            for(int i = (INPUTS_SLOTS + FUEL_SLOTS); i < (INPUTS_SLOTS + FUEL_SLOTS + OUTPUT_SLOTS); i++) {
                this.setInventorySlotContents(i, itemstacks[k].copy());
                k++;
            }
        }
        else {
            int k = 0;
            for(int i = (INPUTS_SLOTS + FUEL_SLOTS); i < (INPUTS_SLOTS + FUEL_SLOTS + OUTPUT_SLOTS); i++) {
                if(this.getStackInSlot(i) != null && itemstacks[k] != null) {
                    this.getStackInSlot(i).stackSize += itemstacks[k].copy().stackSize;
                }
                else if(this.getStackInSlot(i) == null) {
                    this.setInventorySlotContents(i, itemstacks[k].copy());;
                }
                k++;
            }
        }
        for(int i = 0; i < INPUTS_SLOTS; i++) {
            this.decrStackSize(i, 1);
        }
        this.markDirty();
    }
    
    

    Puis la fonction qui orchestre les tout, la fonction update()

    
    @Override
    public void update()
    {
        if(this.isRemainingFuel()) {
            --this.remainingFuelTime;
        }
        if(this.isWorking() && this.canWork()) {
            ++this.currentWorkingTime;
        }
        if(!this.isWorking() && this.canWork()) {
            this.currentWorkingTime = 1;
        }
        if(this.canWork() && this.currentWorkingTime >= this.totalWorkingTime) {
            this.applyRecipe();
            this.currentWorkingTime = 0;
        }
        if(!this.canWork()) {
            this.currentWorkingTime = 0;
        }
        this.markDirty();
    }
    
    

    Cette fonction est assez épurée vu qu'on a tout séparé, vous devriez la comprendre sans problème.

    Pour sauvegarder le tout il nous faut envoyer les petits packets

    
    @Override
    public Packet getDescriptionPacket() {
        NBTTagCompound nbttagcompound = new NBTTagCompound();
        this.writeToNBT(nbttagcompound);
        return new S35PacketUpdateTileEntity(this.getPos(), this.getBlockMetadata(), nbttagcompound);
    }
    
    @Override
    public void onDataPacket(NetworkManager net, S35PacketUpdateTileEntity pkt) {
        this.readFromNBT(pkt.getNbtCompound());
        this.worldObj.markBlockRangeForRenderUpdate(this.pos, this.pos);
    }
    
    

    Et la dernière fonction (enfin ?) qui retourne la longueur de la barre de progression
    en fonction de l'avancement de la recette

    
    @SideOnly(Side.CLIENT)
    public int getRecipeProgress() {
        return this.currentWorkingTime * 17 / this.totalWorkingTime;
    }
    
    

    Ici, 17 correspond à la longueur totale de ma barre de progression, mettait la votre.

    Voilà pour la classe du TileEntity

    La classe des recettes

    Allez dans la classe RecipesMachineTuto.
    Dans cette classe nous allons répertorier les recettes et le temps que dure un fuel.

    Déclarez tout d'abord une instance de cette même classe en private, static et final

    private static final RecipesMachineTuto = new RecipesMachineTuto();
    

    Puis nous allons instancier 2 map qui vont contenir nos recettes et nos fuels

    
    private Map <itemstack[],itemstack[]>inputToOutput = new HashMap<itemstack[], itemstack[]="">();
    private Map <itemstack, integer="">fuelToTime = new HashMap<itemstack, integer="">();
    
    

    Juste en dessous nous allons créer une fonction qui renvoie l'instance de notre classe

    
    public static RecipesMachineTuto instance() {
        return INSTANCE;
    }
    
    

    Créons une fonction pour ajouter un fuel et une autre pour récupérer le temps associer à
    cet item

    
    private void addFuel(ItemStack fuel, Integer time) {
        fuelToTime.put(fuel, time);
    }
    
    public int getFuelDuration(ItemStack fuel) {
        Iterator<entry<itemstack, integer="">> iterator = this.fuelToTime.entrySet().iterator();
        Entry <itemstack, integer="">entry;
        do {
            if(!iterator.hasNext()) {
                return 0;
            }
            entry = iterator.next();
        }
        while(fuel.getItem() != entry.getKey().getItem() || fuel.getItemDamage() != entry.getKey().getItemDamage() );
        return entry.getValue();
    }
    
    

    Maintenant on fait la même chose pour les recettes

    
    private void addRecipe(ItemStack[] input, ItemStack[] output) {
        if(input.length == TileEntityMachineTuto.INPUTS_SLOTS && output.length == TileEntityMachineTuto.OUTPUT_SLOTS) {
            inputToOutput.put(input, output);
        }
    }
    
    public ItemStack[] getRecipeResult(ItemStack[] input) {
        if(input.length != TileEntityMachineTuto.INPUTS_SLOTS) return null;
        Iterator<entry<itemstack[], itemstack[]="">> iterator = this.inputToOutput.entrySet().iterator();
        Entry <itemstack[], itemstack[]="">entry;
        do {
            if(!iterator.hasNext()) {
                return null;
            }
            entry = iterator.next();
        }
        while(!this.isSameKey(input, entry.getKey()));
        return entry.getValue();
    }
    
    private boolean isSameKey(ItemStack[] input, ItemStack[] entry) {
        for(int i = 0; i < input.length; i++) {
            if(input*.getItem() != entry*.getItem() || input*.getItemDamage() != entry*.getItemDamage()) return false;
        }
        return true;
    }
    
    

    Et maintenant on crée le constructeur de la classe, c'est dans ce dernier que l'on ajoutera toute nos
    recettes et tout nos fuels (pour les fuels pensez aussi à les ajouter dans le TileEntity dans isFuel)

    
    private RecipesMachineTuto() {
        this.addFuel(new ItemStack(Items.blaze_powder), 1000);
    
        this.addRecipe(new ItemStack[] {
                new ItemStack(Items.stick), new ItemStack(Items.apple),
                new ItemStack(Items.apple), new ItemStack(Items.stick)
        }, new ItemStack[]{
                new ItemStack(Items.golden_apple), new ItemStack(Blocks.planks)
        });
    
        this.addRecipe(new ItemStack[] {
                new ItemStack(Blocks.wool, 1, 1), new ItemStack(Items.apple),
                new ItemStack(Items.apple), new ItemStack(Items.stick)
        }, new ItemStack[]{
                new ItemStack(Items.golden_apple), new ItemStack(Blocks.planks, 1, 1)
        });
    }
    
    

    J'ajoute ici 1 fuel, la poudre de blaze et 2 recettes :
    stick + apple + apple + stick = golden_apple + planks
    orange_wool + apple + apple + stick = golden_apple + spruce_planks

    Voilà pour la classe des recettes

    La classe du container

    Créez maintenant la classe ContainerMachineTuto et étendez-la à Container

    public class ContainerMachineTuto extends Container
    

    Déclarez lui 4 variables

    
    private TileEntityMachineTuto tile;
    private int currentWorkingTime;
    private int totalWorkingTime;
    private int remainingFuelTime;
    
    

    Explication :
    tile fera référence au TileEntity de ce container
    les 3 autres feront référence aux 3 variables du même nom se trouvant dans tile

    On ajoute maintenant les slots et on initialise tile, le tout dans le constructeur

    
    public ContainerMachineTuto(TileEntityMachineTuto tile, InventoryPlayer inventory) {
        this.tile = tile;
    
        //INPUTS
        this.addSlotToContainer(new Slot(tile, 0, 21, 0));
        this.addSlotToContainer(new Slot(tile, 1, 45, 0));
        this.addSlotToContainer(new Slot(tile, 2, 69, 0));
        this.addSlotToContainer(new Slot(tile, 3, 93, 0));
    
        //FUEL
        this.addSlotToContainer(new Slot(tile, 4, 21, 34));
    
        //OUTPUTS
        this.addSlotToContainer(new SlotOutput(tile, 5, 149, 0));
        this.addSlotToContainer(new SlotOutput(tile, 6, 149, 25));
    
        this.bindPlayerInventory(inventory);
    }
    
    private void bindPlayerInventory(InventoryPlayer inventory) {
        int i;
        int j;
        for (i = 0; i < 3; ++i) {
            for (j = 0; j < 9; ++j) {
                this.addSlotToContainer(new Slot(inventory, j + i * 9 + 9, 8 + j * 18, 84 + i * 18 + 12));
            }
        }
    
        for (i = 0; i < 9; ++i) {
            this.addSlotToContainer(new Slot(inventory, i, 8 + i * 18, 142 + 12));
        }
    }
    
    

    J'utilise une classe SlotOutput, créez-la on s'y attaque après.

    Pour transférer un stack dans un slot

    
    @Override
    public ItemStack transferStackInSlot(EntityPlayer player, int quantity) {
        ItemStack itemstack = null;
        Slot slot = (Slot)this.inventorySlots.get(quantity);
        if (slot != null && slot.getHasStack()) {
            ItemStack itemstack1 = slot.getStack();
            itemstack = itemstack1.copy();
            if (quantity < this.tile.getSizeInventory()) {
                if (!this.mergeItemStack(itemstack1, this.tile.getSizeInventory(), this.inventorySlots.size(), true)) {
                    return null;
                }
            }
            else if (!this.mergeItemStack(itemstack1, 0, this.tile.getSizeInventory(), false)) {
                return null;
            }
            if (itemstack1.stackSize == 0) {
                slot.putStack((ItemStack)null);
            }
            else {
                slot.onSlotChanged();
            }
        }
        return itemstack;
    }
    
    

    Peut-on intéragir avec le container ? Pose plutôt la question au tile

    
    @Override
    public boolean canInteractWith(EntityPlayer player)  {
        return this.tile.isUseableByPlayer(player);
    }
    
    

    Lorsqu'on ferme le container on appelle la fonction closeInventory du tile

    
    @Override
    public void onContainerClosed(EntityPlayer player) {
        super.onContainerClosed(player);
        this.tile.closeInventory(player);
    }
    
    

    Et pour mettre à jour les 3 variables présentes plus haut dans le container

    
    @Override
    public void addCraftingToCrafters(ICrafting crafting) {
        super.addCraftingToCrafters(crafting);
        for(int i = 0; i < this.tile.getFieldCount(); i++) {
            crafting.sendProgressBarUpdate(this, i, this.tile.getField(i));
        }
        crafting.func_175173_a(this, this.tile);
    }
    
    @Override
    public void detectAndSendChanges() {
        super.detectAndSendChanges();
        for(int i = 0; i < this.crafters.size(); ++i) {
            ICrafting icrafting = (ICrafting)this.crafters.get(i);
            for(int j = 0; j < this.tile.getFieldCount(); j++) {
                if(this.getField(j) != this.tile.getField(j)) {
                    icrafting.sendProgressBarUpdate(this, j, this.tile.getField(j));
                }
            }
        }
        for(int i = 0; i < this.tile.getFieldCount(); i++) {
            this.setField(i, this.tile.getField(i));
        }
    }
    
    @Override
    @SideOnly(Side.CLIENT)
    public void updateProgressBar(int id, int value) {
        this.tile.setField(id, value);
    }
    
    private int getField(int id) {
        switch(id) {
            case 0:
                return this.currentWorkingTime;
            case 1:
                return this.totalWorkingTime;
            case 2:
                return this.remainingFuelTime;
            default:
                return 0;
        }
    }
    
    private void setField(int id, int value) {
        switch(id) {
            case 0:
                this.currentWorkingTime = value;
                break;
            case 1:
                this.totalWorkingTime = value;
                break;
            case 2:
                this.remainingFuelTime = value;
        }
    }
    
    

    Voilà pour le container

    La classe du SlotResult

    Allez dans la classe SlotOutput et étendez-la à la classe Slot

    public class SlotOutput extends Slot
    

    Mettez comme constructeur ceci :

    
    public SlotOutput(IInventory inventoryIn, int index, int xPosition, int yPosition)  {
        super(inventoryIn, index, xPosition, yPosition);
    }
    
    

    Le but de ce slot et de ne pas pouvoir y mettre d'item ainsi :

    
    @Override
    public boolean isItemValid(ItemStack stack) {
        return false;
    }
    
    

    Et une dernière fonction qui va permettre de déclencher les events lors de craft

    
    @Override
    public void onPickupFromSlot(EntityPlayer player, ItemStack stack) {
        super.onCrafting(stack);
        super.onPickupFromSlot(player, stack);
    }
    
    

    Voilà pour la classe SlotOutput

    La classe du GUI

    Dernière étape, le GUI. Créez la classe GuiMachineTuto et mettez-la dans le package client.
    Il faut étendre la classe à GuiContainer

    public class GuiMachineTuto extends GuiContainer
    

    Il faut ensuite déclarer 3 variables

    
    private static final ResourceLocation texture = new ResourceLocation(ModTutoriel.MODID,"textures/gui/guiMachineTuto.png");
    private InventoryPlayer playerInv;
    private TileEntityMachineTuto tile;
    
    

    Explications :
    texture : lien vers la texture à changer
    playerInv : inventaire du joueur
    tile : tile associé à ce gui, sera le même que celui du container

    A présent mettez le constructeur suivant

    
    public GuiMachineTuto(TileEntityMachineTuto tile, InventoryPlayer inventory)  {
        super(new ContainerMachineTuto(tile, inventory));
        this.playerInv = inventory;
        this.tile = tile;
    }
    
    

    Les deux fonctions suivantes permettent de dessiner le Gui :

    • drawGuiContainerBackgroundLayer permet de dessiner l'arrière plan
    • drawGuiContainerForegroundLayer permet de dessiner le permier plan

    Vous pouvez dessiner à l'aide des fonctions :

    this.drawTexturedModalRect(int x, int y, int textureX, int textureY, int width, int height)
    

    x correspond à la coordonnée x de l'endroit où vous voulez afficher votre texture
    y correspond à la coordonnée y de l'endroit où vous voulez afficher votre texture
    textureX correspond à la coordonnée x du morceau de texture que vous voulez afficher
    textureY correspond à la coordonnée y du morceau de texture que vous voulez afficher
    width correspond à largeur du morceau de texture que vous voulez afficher
    height correspond à la hauteur du morceau de texture que vous voulez afficher

    Quand vous utilisez cette fonction, il faut associer la texture au textureManager de minecraft, il faut
    donc mettre 1 fois au début de la fonction

    
    GL11.glColor4f(1.0F, 1.0F, 1.0F, 1.0F); //Permet d'avoir les couleurs
    this.mc.getTextureManager().bindTexture(texture);
    
    

    On peut écrire à l'aide de cette fonction

    this.fontRendererObj.drawString(String texte, int x, int, y, int color)
    

    texte est le texte que vous voulez afficher
    x est la coordonnée x de l'endroit où vous voulez l'afficher
    y est la coordonnée y de l'endroit où vous voulez l'afficher
    color est la couleur du texte

    Pour notre machineTuto j'ai codé ceci :

    
    @Override
    protected void drawGuiContainerBackgroundLayer(float partialTicks, int mouseX, int mouseY)  {
        GL11.glColor4f(1.0F, 1.0F, 1.0F, 1.0F);
        this.mc.getTextureManager().bindTexture(texture);
        int k = (this.width - this.xSize) / 2;
        int l = (this.height - this.ySize) / 2;
        this.drawTexturedModalRect(k - 14, l - 37, 0, 0, 210, 215);
        if(this.tile.isRemainingFuel()) {
            this.drawTexturedModalRect(282, 117, 21, 220, 94, 19);
        }
        if(this.tile.isWorking()) {
            this.drawTexturedModalRect(379, 97, 213, 0, this.tile.getRecipeProgress(), 40);
        }
    }
    
    @Override
    protected void drawGuiContainerForegroundLayer(int x, int y) {
        this.fontRendererObj.drawString(this.playerInv.hasCustomName() ? this.playerInv.getName() : I18n.format(this.playerInv.getName()), 8, this.ySize - 84, 4210752);
        this.fontRendererObj.drawString(this.tile.getName(), 4, -20, 4210752);
    }
    
    

    Avec cette texture

    Les valeurs ont été ajustées en debug, le petit scarabé à côté du bonton play, en debug mode vous pouvez changer les
    valeurs dans les fonctions et ce sera automatiquement mis à jour en jeu. (Pour le container il fait rouvrir le GUI)

    Il vous reste plus qu'à mettre le GuiHandlerTuto comme il le faut, aller je vous le donne

    
    public class GuiHandlerTuto implements IGuiHandler {
    
        @Override
        public Object getServerGuiElement(int ID, EntityPlayer player, World world, int x, int y, int z) {
            TileEntity tile = world.getTileEntity(new BlockPos(x, y, z));
    
            if(tile instanceof TileEntityMachineTuto) {
                return new ContainerMachineTuto((TileEntityMachineTuto)tile, player.inventory);
            }
            return null;
        }
    
        @Override
        public Object getClientGuiElement(int ID, EntityPlayer player, World world, int x, int y, int z) {
            TileEntity tile = world.getTileEntity(new BlockPos(x, y, z));
    
            if(tile instanceof TileEntityMachineTuto) {
                return new GuiMachineTuto((TileEntityMachineTuto)tile, player.inventory);
            }
            return null;
        }
    
    }
    
    

    Bonus

    Dans cette partie nous ferons en sorte que la recette de s'enclenche pas s'il n'y a pas
    assez d'eau dans la machine et nous rajouterons au passage la progression de la barre du fuel.

    Dans la classe du TileEntity rajoutez 3 variables :

    
    private int totalCurrentFuelTime;
    private int remainingWaterQuantity = 0;
    private int totalWaterCapacity = 10000;
    
    

    Explications :

    • totalCurrentFuelTime va contenir le temps de fuel que procure le fuel actuel, on va s'en
      servir pour la progression de la barre du fuel
    • remainingWaterQuantity retiendra la quantité d'eau restante
    • totalWaterCapacity contiendra la valeur max d'eau que peut contenir notre machine, on a
      mis ici 10000 millibuckets soit 10 sceaux d'eau

    Il faut ensuite les enregistrer dans les NBT, je vous laisse le faire, rappellez-vous,
    il faut les écrire et les lires dans le même ordres, pensez donc bien à modifier les deux
    fonctions : read et write

    Il faut ensuite rajouter dans les fonctions getField et setField l'accès aux variables
    dernièrement crées en les rajoutant dans les switch.
    La fonction getFieldCount doit maintenant renvoyer 6, modifiez-la pour qu'elle renvoie
    bien 6.

    Il faut associer une valeur à totalCurrentFuelTime dans la fonction outOfFuel, on le fait au même
    moment que l'on associe la durée du fuel à remainingFuelTime ainsi :

    
    //Dans la fonction outOfFuel
    int duration = RecipesMachineTuto.instance().getFuelDuration(this.getStackInSlot(i));
    if(duration > 0)
    {
        this.totalCurrentFuelTime = duration; //Ligne à rajouter
        this.remainingFuelTime = duration;
        this.decrStackSize(i, 1);
    }
    
    

    La focntion canWork change aussi, on va maintenant vérifier si on a assez d'eau pour
    lancer la recette, il faut rajouter

    if(this.remainingWaterQuantity < this.totalWorkingTime - this.currentWorkingTime) return false;
    

    Juste au dessus de

    ItemStack[] itemstacks = RecipesMachineTuto.instance().getRecipeResult(this.getInputSlotsContents());
    

    Dans la focntion update on va descendre le niveau de l'eau en même temps que l'on augmente le temps
    actuel de la recette, on se retrouve donc avec

    
    @Override
    public void update() {
        if(this.isRemainingFuel()) {
            --this.remainingFuelTime;
        }
        if(this.isWorking() && this.canWork()) {
            ++this.currentWorkingTime;
            --this.remainingWaterQuantity;
        }
        if(!this.isWorking() && this.canWork()) {
            this.currentWorkingTime = 1;
        }
        if(this.canWork() && this.currentWorkingTime >= this.totalWorkingTime) {
            this.applyRecipe();
            this.currentWorkingTime = 0;
        }
        if(!this.canWork()) {
            this.currentWorkingTime = 0;
        }
        this.markDirty();
    }
    
    

    Et puis on ajoute deux fonction pour récupérer la longueur de texture affichée
    de la progress bar en fonction, ici du niveau d'eau et l'autre du niveau de fuel

    
    @SideOnly(Side.CLIENT)
    public int getFuelProgress() {
        return this.remainingFuelTime * 20 / this.totalCurrentFuelTime;
    }
    
    @SideOnly(Side.CLIENT)
    public int getWaterProgress() {
        return this.remainingWaterQuantity * 128 / this.totalWaterCapacity;
    }
    
    

    Ici 20 et 128 sont la longueur totale de mes 2 barres de progression.
    Mettez vos propres valeurs.

    Voilà pour la modification de la classe du TileEntity

    Dans la classe du container il faut aussi ajouter les 3 variables que nous avons ajouter à notre TileEntity
    Déclarez-les à la suite des autres que nous avons déjà déclaré.

    Ainsi il faut ajouter ces variables dans les swtich des fonctions getField et setField du container
    à la même manière du TileEntity

    Et je repasse la classe du GUI parce que j'ai ajusté les valeurs

    
    public class GuiMachineTuto extends GuiContainer {
    
        private static final ResourceLocation texture = new ResourceLocation(ModTutoriel.MODID,"textures/gui/guiMachineTuto.png");
        private InventoryPlayer playerInv;
        private TileEntityMachineTuto tile;
    
        public GuiMachineTuto(TileEntityMachineTuto tile, InventoryPlayer inventory)  {
            super(new ContainerMachineTuto(tile, inventory));
            this.playerInv = inventory;
            this.tile = tile;
        }
    
        @Override
        protected void drawGuiContainerBackgroundLayer(float partialTicks, int mouseX, int mouseY)  {
            GL11.glColor4f(1.0F, 1.0F, 1.0F, 1.0F);
            this.mc.getTextureManager().bindTexture(texture);
            int k = (this.width - this.xSize) / 2;
            int l = (this.height - this.ySize) / 2;
            this.drawTexturedModalRect(k - 14, l - 37, 0, 0, 210, 215);
            if(this.tile.isRemainingFuel()) {
                this.drawTexturedModalRect(282, 116, 21, 219, 73, this.tile.getFuelProgress());
            }
            if(this.tile.isWorking()) {
                this.drawTexturedModalRect(378, 107, 213, 11, this.tile.getRecipeProgress(), 6);
            }
            this.drawTexturedModalRect(281, 152, 100, 219, this.tile.getWaterProgress(), 7);
        }
    
        @Override
        protected void drawGuiContainerForegroundLayer(int x, int y) {
            this.fontRendererObj.drawString(this.playerInv.hasCustomName() ? this.playerInv.getName() : I18n.format(this.playerInv.getName()), 8, this.ySize - 84, 4210752);
            this.fontRendererObj.drawString(this.tile.getName(), 4, -20, 4210752);
        }
    }
    
    

    Il faut utiliser cette texture là :

    Il faut modifier la classe du bloc pour que lorsque l'on clique droit sur le bloc avec un sceau
    d'eau on ajoute de l'eau et quand on clique droit avec un sceau vide, on récupère de l'eau.

    
    @Override
    public boolean onBlockActivated(World world, BlockPos pos, IBlockState state, EntityPlayer player, EnumFacing side, float hitX, float hitY, float hitZ) {
        //On vérifie que le joueur tient un sceau vide ou un sceau d'eau
        if(player.getHeldItem() != null && (player.getHeldItem().getItem() == Items.water_bucket || player.getHeldItem().getItem() == Items.bucket)) {
            //Si le tile à la position du bloc est bien le notre
            if(world.getTileEntity(pos) instanceof TileEntityMachineTuto) {
                //On le récupère
                TileEntityMachineTuto tile = (TileEntityMachineTuto)world.getTileEntity(pos);
                //Si l'item que l'on tient est un sceau vide
                if(player.getHeldItem().getItem() == Items.bucket) {
                    //On regarde si il reste assez d'eau pour en enlevez l'équivalent d'un sceau
                    if((tile.getField(4) - 1000) >= 0) {
                        //Si c'est le cas on enlève la quantité d'eau
                        tile.setField(4, tile.getField(4) - 1000);
                        //Si en enlevant 1 sceau il n'en reste plus
                        if(--player.inventory.mainInventory[player.inventory.currentItem].stackSize <= 0) {
                            //On met un sceau plein dans sa main
                            player.inventory.mainInventory[player.inventory.currentItem] = new ItemStack(Items.water_bucket, 1, 0);
                        }
                        //Sinon
                        else {
                            //Si il n'y a plus de place dans l'inventaire
                            if(!player.inventory.addItemStackToInventory(new ItemStack(Items.water_bucket))) {
                                //On le drop
                                player.dropPlayerItemWithRandomChoice(new ItemStack(Items.water_bucket, 1, 0), false);
                            }
                        }
                        return true;
                    }
                }
                //Même raisonnement
                else if(player.getHeldItem().getItem() == Items.water_bucket) {
                    if((tile.getField(4) + 1000) <= tile.getField(5)) {
                        tile.setField(4, tile.getField(4) + 1000);
                        if(--player.inventory.mainInventory[player.inventory.currentItem].stackSize <= 0) {
                            player.inventory.mainInventory[player.inventory.currentItem] = new ItemStack(Items.bucket, 1, 0);
                        }
                        else {
                            if(!player.inventory.addItemStackToInventory(new ItemStack(Items.bucket))) {
                                player.dropPlayerItemWithRandomChoice(new ItemStack(Items.bucket, 1, 0), false);
                            }
                        }
                        return true;
                    }
                }
            }
        }
        //Si il ne tient pas l'item que l'on veut on ouvre le gui
        else {
            if(world.isRemote) {
                return true;
            }
            else {
                player.openGui(TestMod.instance, 0, world, pos.getX(), pos.getY(), pos.getZ());
                return true;
            }
        }
        return false;
    }
    
    

    Résultat

    Commit sur GitHub


  • Rédacteurs

    Plus qu'à regarder si on garde ou pas, si on le garde je commit sur GitHub le code



  • Beau tuto ! Bien joué 😃



  • Merci beaucoup pour ce tuto. 🙂


  • Banned

    STP REMET LE GITHUB


Log in to reply