[1.12] Créer un 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 2 objets en entrée, 2 objets pour l'alimenter et qui sortira 1 objet.
    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 soyez attentif.

    Pré-requis

    Code

    La classe principale

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

    public static final Block CUSTOM_FURNACE = new BlockCustomFurnace().setRegistryName("modid:custom_furnace");
    

    Pensez à respecter la convention Java, cette variable est static et final, utilisez donc le SNAKE_CASE.

    Créez la classe BlockCustomFurnace.

    Enregistrez alors votre bloc :

    @SubscribeEvent
    public static void registerBlocks(RegistryEvent.Register<Block> event) {
        event.getRegistry().register(ModBlocks.CUSTOM_FURNACE);
    }
    

    Dans la méthode preInit de votre classe principale, enregistrez votre TileEntity :

    GameRegistry.registerTileEntity(TileCustomFurnace.class, "modid:tile_custom_furnace");
    

    Créez la classe TileCustomFurnace.

    Il faut aussi enregistrer le GUI Handler, mais ça devrait déjà être fait grâce aux pré-requis :

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

    Voilà pour la classe principale.

    La classe du bloc

    Allez maintenant dans la classe du bloc et faite-la hériter à BlockContainer :

    public class BlockCustomFurnace extends BlockContainer
    

    Ajoutez le constructeur :

    public BlockCustomFurnace() {
        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 TileCustomFurnace();
    }
    

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

    @Override
    public void breakBlock(World worldIn, BlockPos pos, IBlockState state) {
        TileEntity tileentity = worldIn.getTileEntity(pos);
    
        if (tileentity instanceof TileCustomFurnace) {
            InventoryHelper.dropInventoryItems(worldIn, pos,
                    (TileCustomFurnace) tileentity);
        }
    
        super.breakBlock(worldIn, 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, EnumHand hand, EnumFacing facing, float hitX, float hitY, float hitZ) {
        if (world.isRemote) {
            return true;
        } else {
            TileEntity tileentity = world.getTileEntity(pos);
    
            if (tileentity instanceof TileCustomFurnace) {
                player.openGui(ModTuto.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 EnumBlockRenderType getRenderType(IBlockState state) {
        return EnumBlockRenderType.MODEL;
    }
    

    Et ceci pour mettre un nom personnalisé au bloc une fois posé :

    @Override
    public void onBlockPlacedBy(World worldIn, BlockPos pos, IBlockState state, EntityLivingBase placer, ItemStack stack) {
        if (stack.hasDisplayName()) {
            TileEntity tileentity = worldIn.getTileEntity(pos);
    
            if (tileentity instanceof TileCustomFurnace) {
                ((TileCustomFurnace) tileentity).setCustomName(stack
                        .getDisplayName());
            }
        }
    }
    

    Vous avez sûrement des errreurs, ignorez-les, nous n'avons pas encore créé les différentes méthodes.

    Voilà pour la classe du bloc.

    La classe du TileEntity

    Allez dans la classe du TileCustomFurnace.

    On va déclarer plusieurs variables

    private NonNullList<ItemStack> stacks = NonNullList.withSize(5, ItemStack.EMPTY);
    private String customName;
    private int	timePassed = 0;
    private int	burningTimeLeft	= 0;
    

    Quelques explications :

    • customName contient le nom personnalisé du bloc si il en a un
    • stacks contient les ItemStack de votre bloc autrement dit tout les slots, c'est ici que sont stockés les items
    • timePassed contient l'avancement de la recette, il représente le temps passé
    • burningTimeLeft contient le temps restant avant avant qu'il n'y est plus de feux
      Maintenant faisont hériter la classe à TileEntityLockable et implémentons ITickable
    public class TileCustomFurnace extends TileEntityLockable implements ITickable
    

    N'implémentez 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 quand on sait utiliser les NBT :

    @Override
    public void readFromNBT(NBTTagCompound compound) {
        super.readFromNBT(compound);
        this.stacks = NonNullList.<ItemStack>withSize(this.getSizeInventory(), ItemStack.EMPTY);
        ItemStackHelper.loadAllItems(compound, this.stacks);
    
        if (compound.hasKey("CustomName", 8)) {
            this.customName = compound.getString("CustomName");
        }
        this.burningTimeLeft = compound.getInteger("burningTimeLeft");
        this.timePassed = compound.getInteger("timePassed");
    }
    
    @Override
    public NBTTagCompound writeToNBT(NBTTagCompound compound) {
        super.writeToNBT(compound);
        ItemStackHelper.saveAllItems(compound, this.stacks);
    
        if (this.hasCustomName()) {
            compound.setString("CustomName", this.customName);
        }
    
        compound.setInteger("burningTimeLeft", this.burningTimeLeft);
        compound.setInteger("timePassed", this.timePassed);
    
        return compound;
    }
    

    La fonction hasCustomName n'existe pas, nous allons la créer et tout ce qui va avec :

    @Override
    public boolean hasCustomName() {
        return this.customName != null && !this.customName.isEmpty();
    }
    
    @Override
    public String getName() {
        return hasCustomName() ? this.customName : "tile.custom_furnace";
    }
    
    public void setCustomName(String name) {
        this.customName = name;
    }
    

    Nous allons à présent créer les fonctions qui vont permettre d'accéder aux variables burningTimeLeft et timePassed :

    @Override
    public int getField(int id) {
        switch (id) {
            case 0:
                return this.burningTimeLeft;
            case 1:
                return this.timePassed;
        }
        return 0;
    }
    
    @Override
    public void setField(int id, int value) {
        switch (id) {
            case 0:
                this.burningTimeLeft = value;
                break;
            case 1:
                this.timePassed = value;
        }
    }
    
    @Override
    public int getFieldCount() {
        return 2;
    }
    

    Ce sont juste des getters et setters.

    Maintenant, créons les fonctions qui permettrons de manipuler les ItemStack de nos slots :

    @Override
    public int getSizeInventory() {
        return this.stacks.size();
    }
    
    @Override
    public ItemStack getStackInSlot(int index) {
        return this.stacks.get(index);
    }
    
    @Override
    public ItemStack decrStackSize(int index, int count) {
        return ItemStackHelper.getAndSplit(this.stacks, index, count);
    }
    
    @Override
    public ItemStack removeStackFromSlot(int index) {
        return ItemStackHelper.getAndRemove(stacks, index);
    }
    
    @Override
    public void setInventorySlotContents(int index, ItemStack stack) {
        this.stacks.set(index, stack);
    
        if (stack.getCount() > this.getInventoryStackLimit()) {
            stack.setCount(this.getInventoryStackLimit());
        }
    }
    
    @Override
    public int getInventoryStackLimit() {
        return 64;
    }
    
    @Override
    public boolean isEmpty() {
        for(ItemStack stack : this.stacks) {
            if (!stack.isEmpty()) {
                return false;
            }
        }
        return true;
    }
    
    @Override
    public void clear() {
        for(int i = 0; i < this.stacks.size(); i++) {
            this.stacks.set(i, ItemStack.EMPTY);
        }
    }
    

    Toutes ces fonctions sont très simples, je ne vais pas les détaillés, l'interface IInventory indique de toute façon leur utilité.
    Rajoutez aussi ces deux fonctions qui seront appelées lors de l'ouverture et de la fermeture de l'inventaire :

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

    Rajoutez aussi ceux deux autres fonctions qui ne sont utiles qu'à Minecraft :

    @Override
    public Container createContainer(InventoryPlayer playerInventory, EntityPlayer playerIn) {
        return null;
    }
    
    @Override
    public String getGuiID() {
        return null;
    }
    

    Maintenant nous allons définir ce que peut contenir chaque slot (cette fonction ne sert que pour l'automatisation, pas pour le GUI) :

    @Override
    public boolean isItemValidForSlot(int index, ItemStack stack) {
        // Le slot 3 je n'autorise que les planches de bois
        if (index == 2)
            return OreDictionary.getOres("plankWood").contains(
                    new ItemStack(stack.getItem(), 1,
                            OreDictionary.WILDCARD_VALUE));
        // Le slot 4 je n'autorise que le blé
        if (index == 3)
            return stack.getItem() == Items.WHEAT;
        // Le slot 5 (celui du résultat) je n'autorise rien
        if (index == 4)
            return false;
        // Sinon pour les slots 1 et 2 on met ce qu'on veut
        return true;
    }
    

    Il nous faut aussi une fonction qui sera appelée depuis le Container pour savoir si le joueur peut utiliser l'inventaire :

    /** Vérifie la distance entre le joueur et le bloc et que le bloc soit toujours présent */
    public boolean isUsableByPlayer(EntityPlayer player) {
        return this.world.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;
    }
    

    Maintenant nous allons nous occuper du processus de cuisson, afin de nous aider nous allons créer quelques fonctions :

    public boolean hasFuelEmpty() {
        return this.getStackInSlot(2).isEmpty()
                || this.getStackInSlot(3).isEmpty();
    }
    

    Permet de savoir si une slot de carburant est vide.

    public ItemStack getRecipeResult() {
        return RecipesCustomFurnace.getRecipeResult(new ItemStack[] {
                this.getStackInSlot(0), this.getStackInSlot(1) });
    }
    

    La classe RecipesCustomFurnace n'existe toujours pas, c'est la partie d'après. Cette fonction sert à récupérer la recette associée aux ingrédients.

    public boolean canSmelt() {
        // On récupère le résultat de la recette
        ItemStack result = this.getRecipeResult();
    
        // Le résultat est null si il n'y a pas de recette associée, donc on retourne faux
        if (result != null) {
    
            // On récupère le contenu du slot de résultat
            ItemStack slot4 = this.getStackInSlot(4);
    
            // Si il est vide on renvoie vrai
            if (slot4.isEmpty())
                return true;
    
            // Sinon on vérifie que ce soit le même objet, les même métadata et que la taille finale ne sera pas trop grande
            if (slot4.getItem() == result.getItem() && slot4.getItemDamage() == result.getItemDamage()) {
                int newStackSize = slot4.getCount() + result.getCount();
                if (newStackSize <= this.getInventoryStackLimit() && newStackSize <= slot4.getMaxStackSize()) {
                    return true;
                }
            }
        }
        return false;
    }
    

    Cette fonction renvoie vrai si on peut faire cuire les ingrédients, c'est à dire que les ingrédients sont bons et que le résultat de la recette
    peut être mis dans le slot du résultat.

    Nous allons à présent rajouter la fonction qui fait cuire les ingrédients (qui transforme les ingrédient en résultat de la recette) :

    public void smelt() {
        // Cette fonction n'est appelée que si result != null, c'est pourquoi on ne fait pas de null check
        ItemStack result = this.getRecipeResult();
        // On enlève un item de chaque ingrédient
        this.decrStackSize(0, 1);
        this.decrStackSize(1, 1);
        // On récupère le slot de résultat
        ItemStack stack4 = this.getStackInSlot(4);
        // Si il est vide
        if (stack4.isEmpty()) {
            // On y insère une copie du résultat
            this.setInventorySlotContents(4, result.copy());
        } else {
            // Sinon on augmente le nombre d'objets de l'ItemStack
            stack4.setCount(stack4.getCount() + result.getCount());
        }
    }
    

    Et trois dernière fonctions auxiliaires pour nous aider :

    /** Temps de cuisson de la recette */
    public int getFullRecipeTime() {
        return 200;
    }
    
    /** Temps que dure 1 unité de carburant (ici : 1 planche + 1 blé) */
    public int getFullBurnTime() {
        return 300;
    }
    
    /** Renvoie vrai si le feu est allumé */
    public boolean isBurning() {
        return burningTimeLeft > 0;
    }
    

    Nous allons implémenter notre toute dernière fonction, celle de ITickable :

    @Override
    public void update() {
    
    }
    

    Elle est appelée à chaque tick. Tout ce qui se trouve dans la fonction devra être exécuté côté serveur donc :

    @Override
    public void update() {
        if (!this.world.isRemote) {
    
        }
    }
    

    Tout d'abord si le four est allumé on va diminuer le temps restant du feu :

    @Override
    public void update() {
        if (!this.world.isRemote) {
    
            /* Si le carburant brûle, on réduit réduit le temps restant */
            if (this.isBurning()) {
                this.burningTimeLeft--;
            }
        }
    }
    

    Si le four n'est pas allumé, que le la recette est bonne et qu'il y a du carburant, alors on allume le four :

    @Override
    public void update() {
        if (!this.world.isRemote) {
    
            /* Si le carburant brûle, on réduit réduit le temps restant */
            if (this.isBurning()) {
                this.burningTimeLeft--;
            }
    
            /*
                * Si la on peut faire cuire la recette et que le four ne cuit pas
                * alors qu'il peut, alors on le met en route
                */
            if (!this.isBurning() && this.canSmelt() && !this.hasFuelEmpty()) {
                this.burningTimeLeft = this.getFullBurnTime();
                this.decrStackSize(2, 1);
                this.decrStackSize(3, 1);
            }
        }
    }
    

    Et maintenant si le four est allumé et que la recette est bonne, alors on augmente l'avancement de la recette. Si l'avancement de la recette est au maximum
    alors on cuit les ingrédients.

    @Override
    public void update() {
        if (!this.world.isRemote) {
    
            /* Si le carburant brûle, on réduit réduit le temps restant */
            if (this.isBurning()) {
                this.burningTimeLeft--;
            }
    
            /*
                * Si la on peut faire cuire la recette et que le four ne cuit pas
                * alors qu'il peut, alors on le met en route
                */
            if (!this.isBurning() && this.canSmelt() && !this.hasFuelEmpty()) {
                this.burningTimeLeft = this.getFullBurnTime();
                this.decrStackSize(2, 1);
                this.decrStackSize(3, 1);
            }
    
            /* Si on peut faire cuire la recette et que le feu cuit */
            if (this.isBurning() && this.canSmelt()) {
                this.timePassed++;
                if (timePassed >= this.getFullRecipeTime()) {
                    timePassed = 0;
                    this.smelt();
                }
            } else {
                timePassed = 0;
            }
            this.markDirty();
        }
    }
    

    Voilà, à présent nous allons passer à la classe des recettes.

    La classe des recettes

    Créez la classe RecipesCustomFurnace si ce n'est pas fait. Déclarez la HashMap suivante :

    private static final HashMap <ItemStack[], ItemStack>recipes = new HashMap<ItemStack[], ItemStack>();
    static {
        addRecipe(Items.APPLE, Items.ARROW, Items.BAKED_POTATO);
    }
    

    Elle permettra de faire le lien entre les ingrédients et le résultat des recettes.
    Le scope static permet de rajouter une recette.

    Déclarons maintenant les fonctions pour ajouter les recettes :

    private static void addRecipe(Item ingredient1, Item ingredient2, Item resultat1) {
        addRecipe(new ItemStack(ingredient1), new ItemStack(ingredient2), new ItemStack(resultat1));
    }
    
    private static void addRecipe(ItemStack ingredient1, ItemStack ingredient2, ItemStack resultat1) {
        recipes.put(new ItemStack[]{ingredient1, ingredient2}, resultat1);
    }
    

    Créons aussi cette fonction pour comparer les ItemStack :

    private static boolean areKeysEqual(ItemStack[] key1, ItemStack[] key2) {
        if(key1.length != key2.length) return false;
    
        for(int i = 0; i < key1.length; i++) {
            ItemStack s1 = key1***;
            ItemStack s2 = key2***;
            if(s1.isEmpty() && !s2.isEmpty()) return false;
            if(!s1.isEmpty() && s2.isEmpty()) return false;
            if(s1.getItem() != s2.getItem()) return false;
            if(s1.getItemDamage() != s2.getItemDamage()) return false;
        }
        return true;
    }
    

    Et la fonction qui permet de trouver la recette :

    public static ItemStack getRecipeResult(ItemStack[] ingredients) {
        Iterator<Entry<ItemStack[], ItemStack>> it = recipes.entrySet().iterator();
        while(it.hasNext()) {
            Entry <ItemStack[], ItemStack>entry = it.next();
            if(areKeysEqual(entry.getKey(), ingredients)) {
                return entry.getValue();
            }
        }
        return null;
    }
    

    La classe du container

    Créez la classe ContainerCustomFurnace, faites-la hériter de Container.
    Déclarer 3 variables :

    private TileCustomFurnace tile;
    private int	timePassed = 0;
    private int	burnTimeLeft = 0;
    

    On ajoute les slots au container dans le constructeur :

    public ContainerCustomFurnace(TileCustomFurnace tile, InventoryPlayer playerInventory) {
        this.tile = tile;
    
        int i;
        for(i = 0; i < 2; i++) {
            this.addSlotToContainer(new Slot(tile, i, 33 + i * 18, 7));
        }
        for(i = 0; i < 2; i++) {
            this.addSlotToContainer(new SlotSingleItem(tile, i + 2, 42, 40 + i * 18, i == 0 ? Item.getItemFromBlock(Blocks.PLANKS) : Items.WHEAT));
        }
        this.addSlotToContainer(new SlotOutput(tile, 4, 116, 17));
    
        for(i = 0; i < 3; ++i) {
            for(int j = 0; j < 9; ++j) {
                this.addSlotToContainer(new Slot(playerInventory, j + i * 9 + 9, 8 + j * 18, 84 + i * 18));
            }
        }
    
        for(i = 0; i < 9; ++i) {
            this.addSlotToContainer(new Slot(playerInventory, i, 8 + i * 18, 142));
        }
    }
    

    Les slots SlotSingleItem et SlotOutput sont des slots créés par moi-même, on verra ça après.
    La focntion pour savoir si le joueur peut utiliser le containe :

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

    Et les fonctions pour mettre à jour les valeurs du TileEntity pour l'affichage sur le client :

    @Override
    public void addListener(IContainerListener listener) {
        super.addListener(listener);
        listener.sendAllWindowProperties(this, this.tile);
    }
    
    @Override
    public void detectAndSendChanges() {
        super.detectAndSendChanges();
    
        for(int i = 0; i < this.listeners.size(); ++i) {
            IContainerListener icontainerlistener = (IContainerListener) this.listeners
                    .get(i);
    
            if (this.burnTimeLeft != this.tile.getField(0)) {
                icontainerlistener.sendProgressBarUpdate(this, 0,
                        this.tile.getField(0));
            }
    
            if (this.timePassed != this.tile.getField(1)) {
                icontainerlistener.sendProgressBarUpdate(this, 1,
                        this.tile.getField(1));
            }
        }
    
        this.burnTimeLeft = this.tile.getField(0);
        this.timePassed = this.tile.getField(1);
    }
    
    @Override
    @SideOnly(Side.CLIENT)
    public void updateProgressBar(int id, int data) {
        this.tile.setField(id, data);
    }
    

    Si vous êtes en 1.12+, la fonction sendProgressBarUpdate est devenue sendWindowProperty.

    Et la fonction pour gérer le shift+clic, étant donné que cette fonction est relative au nombre de slot choisis, etc ... Je désactive ici le SHIFT + CLIC :

    @Override
    public ItemStack transferStackInSlot(EntityPlayer playerIn, int index) {
        return ItemStack.EMPTY;
    }
    

    On va créer nos deux classe de Slot :

    public class SlotOutput extends Slot {
    
        public SlotOutput(IInventory inventoryIn, int index, int xPosition, int yPosition) {
            super(inventoryIn, index, xPosition, yPosition);
        }
    
        @Override
        public boolean isItemValid(ItemStack stack) {
            return false;
        }
    }
    

    Et :

    public class SlotSingleItem extends Slot {
    
        private Item item;
    
        public SlotSingleItem(IInventory inventoryIn, int index, int xPosition, int yPosition, Item item) {
            super(inventoryIn, index, xPosition, yPosition);
            this.item = item;
        }
    
        @Override
        public boolean isItemValid(ItemStack stack) {
            return stack.isEmpty() || stack.getItem() == item;
        }
    }
    

    La classe du GUI

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

    public class GuiCustomFurnace extends GuiContainer
    

    Il faut ensuite déclarer deux variables :

    private static final ResourceLocation background = new ResourceLocation("modid","textures/gui/container/custom_furnace.png");
    private TileCustomFurnace tile;
    

    Explications :

    • texture : lien vers le fond du GUI
    • tile : TileEntity associé à ce GUI, sera le même que celui du container
      A présent mettez le constructeur suivant :
    public GuiCustomFurnace(TileCustomFurnace tile, InventoryPlayer playerInv) {
            super(new ContainerCustomFurnace(tile, playerInv));
            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

    this.mc.getTextureManager().bindTexture(background);
    

    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 custom_furnace j'ai codé ceci :

    
    @Override
    protected void drawGuiContainerBackgroundLayer(float partialTicks, int mouseX, int mouseY) {
        int i = (this.width - this.xSize) / 2;
        int j = (this.height - this.ySize) / 2;
        this.drawDefaultBackground();
        this.mc.getTextureManager().bindTexture(background);
        this.drawTexturedModalRect(i, j, 0, 0, this.xSize, this.ySize);
    
        int timePassed = this.tile.getField(1);
        int textureWidth = (int) (23f / 200f * timePassed);
        this.drawTexturedModalRect(i + 81, j + 24, 177, 18, textureWidth, 7);
    
        if (this.tile.isBurning()) {
            int burningTime = this.tile.getField(0);
            int textureHeight = (int) (12f / this.tile.getFullBurnTime() * burningTime);
            this.drawTexturedModalRect(i + 37, j + 26 + 12 - textureHeight,
                    177, 12 - textureHeight, 27, textureHeight);
        }
    
        this.fontRenderer.drawString(this.tile.getName(), i + 80, j + 45, 0xFFFFFF);
    }
    

    Avec cette texture :
    0_1529702355595_custom_furnace.png

    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 ré-ouvrir le GUI)

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

    public class GuiHandler implements IGuiHandler {
    
        @Override
        public Object getServerGuiElement(int ID, EntityPlayer player, World world, int x, int y, int z) {
            TileEntity te = world.getTileEntity(new BlockPos(x, y, z));
            if(te instanceof TileCustomFurnace) {
                return new ContainerCustomFurnace((TileCustomFurnace)te, player.inventory);
            }
            return null;
        }
    
        @Override
        public Object getClientGuiElement(int ID, EntityPlayer player, World world, int x, int y, int z) {
            TileEntity te = world.getTileEntity(new BlockPos(x, y, z));
            if(te instanceof TileCustomFurnace) {
                return new GuiCustomFurnace((TileCustomFurnace)te, player.inventory);
            }
            return null;
        }
    }
    

    Résultat

    0_1529702367263_custom_furnace_final.png

    Crédits

    Rédaction :

    • BrokenSwing

    Correction :

    • BrokenSwing

    Creative Commons
    Ce tutoriel de Minecraft Forge France est mis à disposition selon les termes de la licence Creative Commons Attribution - Pas d’Utilisation Commerciale - Partage dans les Mêmes Conditions 4.0 International

    retourRetour vers le sommaire des tutoriels



  • Ouahhhh, TRES bon travail, juste une question, depuis quand tu écris ce sujet, depuis 3mois 😄 je ne vais pas tester, c'est trop long pour moi mais je te dis bravo quand même ^^


  • Rédacteurs

    Je l'ai écrit ce matin et en début d'après-midi, c'est vrai que c'est long mais en organisant correctement tes classes tu peux être capable de créer plusieurs machines sans tout refaire à chaque fois. En tout cas merci.



  • Bonjour,
    J'ai crée mon réacteur nucléaire avec ma classe ReactorRecipes qui enregistre les différentes recette sous cette forme:

    addItemRecipe(Items.COAL, new ItemStack(ItemHandler.radioactiveCoal), 0.35F);
    public void addItemRecipe(Item itemCombustible, ItemStack result, float experience)

    Mais ma question est de savoir comment rajouter un 4ème argument déterminant le temps de la cuisson ? J'ai vaguement vu que tu en parlais dans la classe Container, mais je voudrai le mettre dans ma classe Recipes. Une piste, un indice ? Merci d'avance !



  • Je pense qu'il suffit de changer la méthode getFullRecipeTime() pour donner la valeur en fonction de la recette en cours. Ensuite, dans le Gui, pour que la barre de chargement fonctionne bien, il faut modifier dans la méthode drawGuiBackgroundLayer(), le calcul de textureWidth en changent le 200f en this.tile.getFullRecipeTime().


  • Rédacteurs

    Il faut que tu renvoie une valeur différente suivant les "ingrédients" que tu as dans ta recette ici :

    
    /** Temps de cuisson de la recette */
    public int getFullRecipeTime() {
    return 200;
    }
    
    

    Avec une fonction du type : public static int ReactorRecipes.getTimeNeededFor(ItemStack[] ingredients)



  • Merci à vous deux, je vais essayer !
    EDIT = Tout marche à merveille 😃



  • Ahah, je vais peut etre essayé finalement, j'ai l'idée de quelques chose qui va pas etre piqué des hannetons 😄 je vous demanderais de l'aide si je n'arrive pas !



  • Bonjour j'ai suivi le tutoriel mais j'ai une petite erreur dans ma classe Container dans la méthode pour mettre à jour ma TileEntity voici l'erreur en question :
    sur la ligne ```java
    if (this.burnTimeLeft != this.tile.getField(0)) {
    icontainerlistener.sendProgressBarUpdate(this, 0,
    this.tile.getField(0));
    }

    voici 
    
    • The method sendProgressBarUpdate(ContainerCustomFurnace, int, int) is undefined for the type
      IContainerListener

  • Rédacteurs

    ContainerCustomFurnace hérite bien de Container ?



  • oui l'héritage est bon, je met ma classe Container ci-dessous

    package fr.priya.simpleores.gui;
    
    import net.minecraft.entity.player.EntityPlayer;
    import net.minecraft.entity.player.InventoryPlayer;
    import net.minecraft.init.Blocks;
    import net.minecraft.inventory.InventoryCrafting;
    import net.minecraft.init.Items;
    import net.minecraft.inventory.Container;
    import net.minecraft.inventory.IContainerListener;
    import net.minecraft.inventory.Slot;
    import net.minecraft.item.Item;
    import net.minecraft.item.ItemStack;
    import net.minecraftforge.fml.relauncher.Side;
    import net.minecraftforge.fml.relauncher.SideOnly;
    
    public class ContainerCustomFurnace extends Container{
    private TileEntityCustomFurnace tile;
    private int timePassed = 0;
    private int burnTimeLeft = 0;
    
    public ContainerCustomFurnace(TileEntityCustomFurnace tile, InventoryPlayer playerInventory)
    {
    int i;
    for(i = 0; i < 2; i++)
    {
    this.addSlotToContainer(new Slot(tile, i, 33 + i*18, 7));
    }
    for(i = 0; i < 2; i++)
    {
    this.addSlotToContainer(new SlotSingleItem(tile, i + 2, 42, 40 + i* 18, i == 0 ? Item.getItemFromBlock(Blocks.PLANKS) : Items.WHEAT));
    }
    this.addSlotToContainer(new SlotOutput(tile, 4, 116, 17));
    
    for(i = 0; i < 3; ++i) {
    for(int j = 0; j < 9; ++j) {
    this.addSlotToContainer(new Slot(playerInventory, j + i * 9 + 9, 8 + j * 18, 84 + i * 18));
    }
    }
    
    for(i = 0; i < 9; ++i) {
    this.addSlotToContainer(new Slot(playerInventory, i, 8 + i * 18, 142));
    }
    }
    
    public boolean canInteractWith(EntityPlayer player)
    {
    return tile.isUsableByPlayer(player);
    }
    
    public void addListener(IContainerListener listener) {
    super.addListener(listener);
    listener.sendAllWindowProperties(this, this.tile);
    }
    
    @Override
    public void detectAndSendChanges() {
    super.detectAndSendChanges();
    
    for(int i = 0; i < this.listeners.size(); ++i) {
    IContainerListener icontainerlistener = (IContainerListener) this.listeners.get(i);
    
    if (this.burnTimeLeft != this.tile.getField(0)) {
    icontainerlistener.sendProgressBarUpdate(this, 0,
    this.tile.getField(0));
    }
    
    if (this.timePassed != this.tile.getField(1)) {
    icontainerlistener.sendProgressBarUpdate(this, 1,
    this.tile.getField(1));
    }
    }
    
    this.burnTimeLeft = this.tile.getField(0);
    this.timePassed = this.tile.getField(1);
    }
    
    @SideOnly(Side.CLIENT)
    public void updateProgressBar(int id, int data) {
    this.tile.setField(id, data);
    }
    
    public ItemStack transferStackInSlot(EntityPlayer playerIn, int index) {
    return ItemStack.EMPTY;
    }
    
    }
    
    

  • Rédacteurs

    C'est bizarre parce que la définition est la suivante : IContainerListener#sendProgressBarUpdate(Container containerIn, int varToUpdate, int newValue)
    Donc il n'y a pas de raison pour que cela ne fonctionne pas.


    Ce tutoriel concerne la 1.11, es-tu bien en 1.11 ?



  • Non elle a changé de nom récemment (1.12 je crois), regardes dans le code l'interface IContainerListener il y aura une fonction avec les mêmes arguments c'est celle là.


  • Rédacteurs

    C'est bien pourquoi je précise que ce tutoriel est pour la 1.11.



  • Je suis bien en 1.11 je vais essayer d'update mes mappings pour voir
    EDIT: même avec les derniers mappings ça ne fonctionne pas je l'ai remplacer par la fonction sendWindowProperty et maitenant mon jeu crash lorsque je fait clique droit sur mon block
    Voici les logs

    –-- Minecraft Crash Report ----
    // Surprise! Haha. Well, this is awkward.
    
    Time: 16/07/17 11:56
    Description: Ticking player
    
    java.lang.NullPointerException: Ticking player
    at fr.priya.simpleores.gui.ContainerCustomFurnace.detectAndSendChanges(ContainerCustomFurnace.java:62)
    at net.minecraft.entity.player.EntityPlayerMP.onUpdate(EntityPlayerMP.java:293)
    at net.minecraft.world.World.updateEntityWithOptionalForce(World.java:2152)
    at net.minecraft.world.WorldServer.updateEntityWithOptionalForce(WorldServer.java:879)
    at net.minecraft.world.World.updateEntity(World.java:2119)
    at net.minecraft.world.WorldServer.tickPlayers(WorldServer.java:680)
    at net.minecraft.world.World.updateEntities(World.java:1908)
    at net.minecraft.world.WorldServer.updateEntities(WorldServer.java:651)
    at net.minecraft.server.MinecraftServer.updateTimeLightAndEntities(MinecraftServer.java:795)
    at net.minecraft.server.MinecraftServer.tick(MinecraftServer.java:699)
    at net.minecraft.server.integrated.IntegratedServer.tick(IntegratedServer.java:156)
    at net.minecraft.server.MinecraftServer.run(MinecraftServer.java:548)
    at java.lang.Thread.run(Unknown Source)
    
    A detailed walkthrough of the error, its code path and all known details is as follows:
    ---------------------------------------------------------------------------------------
    
    -- Head --
    Thread: Server thread
    Stacktrace:
    at fr.priya.simpleores.gui.ContainerCustomFurnace.detectAndSendChanges(ContainerCustomFurnace.java:62)
    at net.minecraft.entity.player.EntityPlayerMP.onUpdate(EntityPlayerMP.java:293)
    at net.minecraft.world.World.updateEntityWithOptionalForce(World.java:2152)
    at net.minecraft.world.WorldServer.updateEntityWithOptionalForce(WorldServer.java:879)
    at net.minecraft.world.World.updateEntity(World.java:2119)
    
    -- Player being ticked --
    Details:
    Entity Type: null (net.minecraft.entity.player.EntityPlayerMP)
    Entity ID: 281
    Entity Name: Player177
    Entity's Exact location: 257,83, 64,00, 224,36
    Entity's Block location: World: (257,64,224), Chunk: (at 1,4,0 in 16,14; contains blocks 256,0,224 to 271,255,239), Region: (0,0; contains chunks 0,0 to 31,31, blocks 0,0,0 to 511,255,511)
    Entity's Momentum: 0,00, -0,08, 0,00
    Entity's Passengers: []
    Entity's Vehicle: ~~ERROR~~ NullPointerException: null
    Stacktrace:
    at net.minecraft.world.WorldServer.tickPlayers(WorldServer.java:680)
    at net.minecraft.world.World.updateEntities(World.java:1908)
    at net.minecraft.world.WorldServer.updateEntities(WorldServer.java:651)
    
    -- Affected level --
    Details:
    Level name: New World
    All players: 1 total; [EntityPlayerMP['Player177'/281, l='New World', x=257,83, y=64,00, z=224,36]]
    Chunk stats: ServerChunkCache: 626 Drop: 0
    Level seed: 2481567272931423200
    Level generator: ID 00 - default, ver 1\. Features enabled: true
    Level generator options:
    Level spawn location: World: (252,64,236), Chunk: (at 12,4,12 in 15,14; contains blocks 240,0,224 to 255,255,239), Region: (0,0; contains chunks 0,0 to 31,31, blocks 0,0,0 to 511,255,511)
    Level time: 184333 game time, 824 day time
    Level dimension: 0
    Level storage version: 0x04ABD - Anvil
    Level weather: Rain time: 1 (now: false), thunder time: 1 (now: false)
    Level game mode: Game mode: creative (ID 1). Hardcore: false. Cheats: true
    Stacktrace:
    at net.minecraft.server.MinecraftServer.updateTimeLightAndEntities(MinecraftServer.java:795)
    at net.minecraft.server.MinecraftServer.tick(MinecraftServer.java:699)
    at net.minecraft.server.integrated.IntegratedServer.tick(IntegratedServer.java:156)
    at net.minecraft.server.MinecraftServer.run(MinecraftServer.java:548)
    at java.lang.Thread.run(Unknown Source)
    
    – System Details --
    Details:
    Minecraft Version: 1.11
    Operating System: Windows 10 (amd64) version 10.0
    Java Version: 1.8.0_131, Oracle Corporation
    Java VM Version: Java HotSpot(TM) 64-Bit Server VM (mixed mode), Oracle Corporation
    Memory: 657540448 bytes (627 MB) / 1056309248 bytes (1007 MB) up to 1056309248 bytes (1007 MB)
    JVM Flags: 3 total; -Xincgc -Xmx1024M -Xms1024M
    IntCache: cache: 1, tcache: 1, allocated: 12, tallocated: 94
    FML: MCP 9.35 Powered by Forge 13.19.1.2189 4 mods loaded, 4 mods active
    States: 'U' = Unloaded 'L' = Loaded 'C' = Constructed 'H' = Pre-initialized 'I' = Initialized 'J' = Post-initialized 'A' = Available 'D' = Disabled 'E' = Errored
    UCHIJAAAA mcp{9.19} [Minecraft Coder Pack] (minecraft.jar)
    UCHIJAAAA FML{8.0.99.99} [Forge Mod Loader] (forgeSrc-1.11-13.19.1.2189.jar)
    UCHIJAAAA forge{13.19.1.2189} [Minecraft Forge] (forgeSrc-1.11-13.19.1.2189.jar)
    UCHIJAAAA simpleores{0.1} [Simple ores] (bin)
    Loaded coremods (and transformers):
    GL info: ~~ERROR~~ RuntimeException: No OpenGL context found in the current thread.
    Profiler Position: N/A (disabled)
    Player Count: 1 / 8; [EntityPlayerMP['Player177'/281, l='New World', x=257,83, y=64,00, z=224,36]]
    Type: Integrated Server (map_client.txt)
    Is Modded: Definitely; Client brand changed to 'fml,forge'
    

  • Rédacteurs

    Dans le constructeur de ton container tu as oublié

    this.tile = tile;
    


  • Merci beaucoup tout fonctionne, juste une dernière petite question comment fait-on pour ajouter des recettes pour notre four ?
    J'ai essayé ceci que j'ai mis dans ma classe recettes sans succès ```java
    addRecipe(new Item(SimpleoresItems.CORN), new ItemStack(Items.SUGAR), new ItemStack(SimpleoresItems.CORN_SEED));


  • Rédacteurs

    addRecipe(new ItemStack(SimpleoresItems.CORN), new ItemStack(Items.SUGAR), new ItemStack(SimpleoresItems.CORN_SEED));
    

    Tutoriel testé et corrigé. Il fonctionne et peut être déplacé.



  • Bonjour je n'arrive pas a ajouter mes recettes j'ai essayer de les mettres sous les addRecipes dans ma classe Recettes mais cela ne fonctionne pas

    package fr.priya.simpleores.gui;
    
    import java.util.HashMap;
    import java.util.Iterator;
    import java.util.Map.Entry;
    
    import fr.priya.simpleores.items.SimpleoresItems;
    import net.minecraft.init.Items;
    import net.minecraft.item.Item;
    import net.minecraft.item.ItemStack;
    
    public class RecipesCustomFurnace {
    private static HashMap <itemstack[], itemstack="">recipes = new  HashMap<itemstack[], itemstack="">();
    //ajouter les recettes
    private static void addRecipe(Item ingredient1, Item ingredient2, Item resultat1) {
    addRecipe(new ItemStack(ingredient1), new ItemStack(ingredient2), new ItemStack(resultat1));
    //j'ai essayer de le mettre ici
    }
    
    private static void addRecipe(ItemStack ingredient1, ItemStack ingredient2, ItemStack resultat1) {
    recipes.put(new ItemStack[]{ingredient1, ingredient2}, resultat1);
    //et ici
    }
    
    //comparer les items stacks
    private static boolean areKeysEqual(ItemStack[] key1, ItemStack[] key2) {
    if(key1.length != key2.length) return false;
    
    for(int i = 0; i < key1.length; i++) {
    ItemStack s1 = key1*;
    ItemStack s2 = key2*;
    if(s1.isEmpty() && !s2.isEmpty()) return false;
    if(!s1.isEmpty() && s2.isEmpty()) return false;
    if(s1.getItem() != s2.getItem()) return false;
    if(s1.getItemDamage() != s2.getItemDamage()) return false;
    }
    return true;
    }
    //fonction pour trouver la recette
    public static ItemStack getRecipesResult(ItemStack[] ingredients)
    {
    Iterator<entry<itemstack[], itemstack="">> it = recipes.entrySet().iterator();
    while(it.hasNext()) {
    Entry <itemstack[], itemstack="">entry = it.next();
    if (areKeysEqual(entry.getKey(), ingredients))
    {
    return entry.getValue();
    }
    }
    return null;
    }
    
    }
    
    ```</itemstack[],></entry<itemstack[],></itemstack[],></itemstack[],>

  • Rédacteurs

    Il faut mettre les addRecipe dans un bloc static, regarde ici : https://www.minecraftforgefrance.fr/showthread.php?tid=4386#classerecette