16 juin 2015, 12:34

Sommaire

Introduction

Vous avez déjà créé un coffre ou une TileEntity et vous voudriez faire la même chose sous forme d’Item ? Alors suivez ce tutoriel pour savoir comment faire pour créer un backpack.

Pré-requis

Code

Classe de l’Item :

Tout d’abord il nous faut un item sur lequel appliquer notre container :

package fr.scarex.tutorialmod.item;

import net.minecraft.creativetab.CreativeTabs;
import net.minecraft.entity.Entity;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.item.Item;
import net.minecraft.item.ItemStack;
import net.minecraft.world.World;
import cpw.mods.fml.common.registry.GameRegistry;
import fr.scarex.tutorialmod.TutorialMod;

/**
    * @author SCAREX
    * 
    */
public class ItemBackPack extends Item
{
    public static final String NAME = "backpack";

    public ItemBackPack() {
        this.setUnlocalizedName(TutorialMod.MODID + "_" + NAME);
        this.setTextureName(TutorialMod.MODID + ":" + NAME);
        this.setCreativeTab(CreativeTabs.tabTools);
        this.maxStackSize = 1; // N'oubliez pas ceci, çà empêche l'item d'être stackable

        this.register();
    }

    /**
        * Used to add the item to the game registry
        */
    public final void register() {
        GameRegistry.registerItem(this, NAME);
    }

    /**
        * Used to open the gui
        */
    @Override
    public ItemStack onItemRightClick(ItemStack stack, World world, EntityPlayer player) {
        player.openGui(TutorialMod.INSTANCE, 0, world, (int) player.posX, (int) player.posY, (int) player.posZ);
        return stack;
    }
}

Classe du Gui Handler :

Il nous faut bien sûr un Gui Handler pour gérer tout çà.

package fr.scarex.tutorialmod;

import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.world.World;
import cpw.mods.fml.common.network.IGuiHandler;
import fr.scarex.tutorialmod.client.gui.inventory.GuiBackPack;
import fr.scarex.tutorialmod.inventory.InventoryBackPack;
import fr.scarex.tutorialmod.inventory.container.ContainerBackPack;
import fr.scarex.tutorialmod.item.ItemBackPack;

/**
    * @author SCAREX
    * 
    */
public class CommonProxy implements IGuiHandler
{
    @Override
    public Object getServerGuiElement(int ID, EntityPlayer player, World world, int x, int y, int z) {
        switch (ID) {
        case 0:
            // The last parameter must be a multiple of 9 (e.g: 9, 18, 27, 54)
            // Condition to check if the player has the right item in hand
            if (player.getHeldItem() == null || !(player.getHeldItem().getItem() instanceof ItemBackPack)) return null;
            return new ContainerBackPack(player.inventory, new InventoryBackPack(player.getHeldItem(), 54));
        }
        return null;
    }

    @Override
    public Object getClientGuiElement(int ID, EntityPlayer player, World world, int x, int y, int z) {
        switch (ID) {
        case 0:
            // The last parameter must be a multiple of 9 (e.g: 9, 18, 27, 54)
            // Condition to check if the player has the right item in hand
            if (player.getHeldItem() == null || !(player.getHeldItem().getItem() instanceof ItemBackPack)) return null;
            return new GuiBackPack(player.inventory, new InventoryBackPack(player.getHeldItem(), 54));
        }
        return null;
    }
}

NOTE : ici j’utilise le common proxy comme Gui Handler, vous pouvez le mettre dans une classe à part.

Classe de l’inventaire du backpack :

Tout comme les TileEntity, il nous faut une classe implements IInventory qui stockera le contenu du sac à dos.

package fr.scarex.tutorialmod.inventory;

import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.inventory.IInventory;
import net.minecraft.item.ItemStack;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.nbt.NBTTagList;
import net.minecraftforge.common.util.Constants;
import fr.scarex.tutorialmod.TutorialMod;
import fr.scarex.tutorialmod.item.ItemBackPack;

/**
    * @author SCAREX
    * 
    */
public class InventoryBackPack implements IInventory
{
    public ItemStack[] content;
    public int size;

    public InventoryBackPack(ItemStack container, int size) {
        this.size = size;
        this.content = new ItemStack;
        if (!container.hasTagCompound()) container.setTagCompound(new NBTTagCompound());
        this.readFromNBT(container.getTagCompound());
    }

    /**
        * This methods reads the content of the NBTTagCompound inside the container
        * 
        * @param comp
        *            the container NBTTagCompound
        */
    public void readFromNBT(NBTTagCompound comp) {
        NBTTagList nbtlist = comp.getTagList("Inventory", Constants.NBT.TAG_COMPOUND);
        for (int i = 0; i < nbtlist.tagCount(); i++) {
            NBTTagCompound comp1 = nbtlist.getCompoundTagAt(i);
            int slot = comp1.getInteger("Slot");
            this.content[slot] = ItemStack.loadItemStackFromNBT(comp1);
        }
    }

    /**
        * This methods saves the content inside the container
        * 
        * @param comp
        *            the NBTTagCompound to write in
        */
    public void writeToNBT(NBTTagCompound comp) {
        NBTTagList nbtlist = new NBTTagList();

        for (int i = 0; i < this.size; i++) {
            if (this.content[i] != null) {
                NBTTagCompound comp1 = new NBTTagCompound();
                comp1.setInteger("Slot", i);
                this.content[i].writeToNBT(comp1);
                nbtlist.appendTag(comp1);
            }
        }
        comp.setTag("Inventory", nbtlist);
    }

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

    @Override
    public ItemStack getStackInSlot(int index) {
        return this.content[index];
    }

    @Override
    public ItemStack decrStackSize(int index, int amount) {
        ItemStack stack = getStackInSlot(index);
        if (stack != null) {
            if (stack.stackSize > amount) {
                stack = stack.splitStack(amount);
                if (stack.stackSize == 0) this.content[index] = null;
            } else {
                this.content[index] = null;
            }
        }
        return stack;
    }

    @Override
    public ItemStack getStackInSlotOnClosing(int index) {
        ItemStack stack = getStackInSlot(index);
        if (stack != null) this.content[index] = null;
        return stack;
    }

    @Override
    public void setInventorySlotContents(int index, ItemStack stack) {
        this.content[index] = stack;
    }

    @Override
    public String getInventoryName() {
        return TutorialMod.MODID + ".container.backpack";
    }

    @Override
    public boolean hasCustomInventoryName() {
        return false;
    }

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

    @Override
    public void markDirty() {}

    @Override
    public boolean isUseableByPlayer(EntityPlayer player) {
        return true;
    }

    @Override
    public void openInventory() {}

    @Override
    public void closeInventory() {}

    /**
        * Prevents backpack-ception
        */
    @Override
    public boolean isItemValidForSlot(int index, ItemStack stack) {
        return !(stack.getItem() instanceof ItemBackPack);
    }
}

NOTE : ici j’ai décidé qu’il serait impossible de mettre des backpacks dans des backpacks. Vous pouvez toujours le modifier : il y a des commentaires à chaque endroit où les backpacks sont bloqués.

Classe du container du backpack :

Maintenant il nous faut un container pour gérer notre inventaire côté serveur.

package fr.scarex.tutorialmod.inventory.container;

import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.entity.player.InventoryPlayer;
import net.minecraft.inventory.Container;
import net.minecraft.inventory.Slot;
import net.minecraft.item.ItemStack;
import net.minecraft.nbt.NBTTagCompound;
import fr.scarex.tutorialmod.inventory.InventoryBackPack;
import fr.scarex.tutorialmod.inventory.slot.SlotBackPack;
import fr.scarex.tutorialmod.item.ItemBackPack;

/**
    * @author SCAREX
    * 
    */
public class ContainerBackPack extends Container
{
    public InventoryBackPack invBackpack;
    public int rows;

    public ContainerBackPack(InventoryPlayer playerInv, InventoryBackPack inv) {
        this.invBackpack = inv;
        this.rows = inv.getSizeInventory() / 9;
        int i = (this.rows - 4) * 18;
        int j;
        int k;

        // Adding slots to the backpack
        for (j = 0; j < this.rows; ++j) {
            for (k = 0; k < 9; ++k) {
                this.addSlotToContainer(new SlotBackPack(inv, k + j * 9, 8 + k * 18, 18 + j * 18));
            }
        }

        // Adding player's slots
        for (j = 0; j < 3; ++j) {
            for (k = 0; k < 9; ++k) {
                this.addSlotToContainer(new Slot(playerInv, k + j * 9 + 9, 8 + k * 18, 103 + j * 18 + i));
            }
        }

        for (j = 0; j < 9; ++j) {
            this.addSlotToContainer(new Slot(playerInv, j, 8 + j * 18, 161 + i));
        }
    }

    @Override
    public boolean canInteractWith(EntityPlayer player) {
        return true;
    }

    public void writeToNBT(ItemStack stack) {
        if (!stack.hasTagCompound()) stack.setTagCompound(new NBTTagCompound());
        invBackpack.writeToNBT(stack.getTagCompound());
    }

    @Override
    public ItemStack transferStackInSlot(EntityPlayer player, int index) {
        ItemStack itemstack = null;
        Slot slot = (Slot) this.inventorySlots.get(index);

        if (slot != null && slot.getHasStack()) {
            ItemStack itemstack1 = slot.getStack();
            itemstack = itemstack1.copy();

            // Prevents backpack-ception (backpack inside backpack) with
            // shift-click
            if (itemstack.getItem() instanceof ItemBackPack) return null;

            if (index < this.invBackpack.getSizeInventory()) {
                if (!this.mergeItemStack(itemstack1, this.invBackpack.getSizeInventory(), this.inventorySlots.size(), true)) return null;
            } else if (!this.mergeItemStack(itemstack1, 0, this.invBackpack.getSizeInventory(), false)) { return null; }

            if (itemstack1.stackSize == 0)
                slot.putStack((ItemStack) null);
            else
                slot.onSlotChanged();
        }

        return itemstack;
    }

    /**
        * @param buttonPressed
        *            left click, right click, wheel click, etc.
        * @param flag
        *            category (e.g.: hotbar keys)
        */
    @Override
    public ItemStack slotClick(int slotIndex, int buttonPressed, int flag, EntityPlayer player) {
        // Prevents from removing current backpack
        if (flag == 2 && buttonPressed == player.inventory.currentItem) return null;
        if (slotIndex - this.invBackpack.getSizeInventory() - 27 == player.inventory.currentItem) return null;
        return super.slotClick(slotIndex, buttonPressed, flag, player);
    }

    /**
        * Used to save content
        */
    @Override
    public void onContainerClosed(EntityPlayer player) {
        this.writeToNBT(player.getHeldItem());
        super.onContainerClosed(player);
    }
}

Classe du slot du backpack :

Ici j’utilise un slot custom pour éviter que les joueurs mettent des backpacks dans des backpack. Comme dit avant, vous pouvez utiliser un slot normal si vous voulez que le joueur puisse mettre des backpacks dans des backpacks.

package fr.scarex.tutorialmod.inventory.slot;

import net.minecraft.inventory.IInventory;
import net.minecraft.inventory.Slot;
import net.minecraft.item.ItemStack;
import fr.scarex.tutorialmod.item.ItemBackPack;

/**
    * @author SCAREX
    * 
    */
public class SlotBackPack extends Slot
{
    public SlotBackPack(IInventory inv, int index, int x, int y) {
        super(inv, index, x, y);
    }

    /**
        * Method used to prevent backpack-ception (backpacks inside backpacks)
        */
    @Override
    public boolean isItemValid(ItemStack stack) {
        return !(stack.getItem() instanceof ItemBackPack);
    }
}

Classe du GUI :

Et nous voici à la dernière classe de ce tutoriel : le GUI. Dans cette classe j’ai décidé d’utiliser la texture utilisée par Minecraft et j’ai aussi décidé de mettre une taille custom pour l’inventaire, comme çà si vous voulez utiliser plus ou moins de slot (par exemple selon les metadatas d’un Item pour avoir des sac à dos plus ou moins grands), il vous suffira juste de changer les 2 valeurs dans le GuiHandler.

NOTE : il faut changer LES 2 VALEURS, sinon vous aurez de grande chances de crasher.

package fr.scarex.tutorialmod.client.gui.inventory;

import net.minecraft.client.gui.inventory.GuiContainer;
import net.minecraft.client.resources.I18n;
import net.minecraft.entity.player.InventoryPlayer;
import net.minecraft.util.ResourceLocation;

import org.lwjgl.opengl.GL11;

import fr.scarex.tutorialmod.inventory.InventoryBackPack;
import fr.scarex.tutorialmod.inventory.container.ContainerBackPack;

/**
    * @author SCAREX
    * 
    */
public class GuiBackPack extends GuiContainer
{
    public static final ResourceLocation texture = new ResourceLocation("textures/gui/container/generic_54.png");
    protected InventoryBackPack inv;
    protected InventoryPlayer playerInv;
    public int rows;

    public GuiBackPack(InventoryPlayer playerInv, InventoryBackPack inv) {
        super(new ContainerBackPack(playerInv, inv));
        this.playerInv = playerInv;
        this.inv = inv;
        this.allowUserInput = false;
        // Calculate the number of rows
        this.rows = inv.getSizeInventory() / 9;
        // Height of the GUI using the number of rows
        this.ySize = 114 + this.rows * 18;
    }

    @Override
    protected void drawGuiContainerForegroundLayer(int x, int y) {
        this.fontRendererObj.drawString(I18n.format(this.inv.getInventoryName(), new Object[0]), 8, 6, 4210752);
        this.fontRendererObj.drawString(this.playerInv.hasCustomInventoryName() ? this.playerInv.getInventoryName() : I18n.format(this.playerInv.getInventoryName(), new Object[0]), 8, this.ySize - 96 + 2, 4210752);
    }

    @Override
    protected void drawGuiContainerBackgroundLayer(float prt, int x, int y) {
        GL11.glColor4f(1.0F, 1.0F, 1.0F, 1.0F);
        this.mc.getTextureManager().bindTexture(texture);

        // Centering GUI
        int k = (this.width - this.xSize) / 2;
        int l = (this.height - this.ySize) / 2;

        // Drawing the first part of the GUI (slots of the backpack)
        this.drawTexturedModalRect(k, l, 0, 0, this.xSize, this.rows * 18 + 17);
        // And after the slots from the player's inventory
        this.drawTexturedModalRect(k, l + this.rows * 18 + 17, 0, 126, this.xSize, 96);
    }
}

Bonus

Vous aimeriez que vos utilisateurs puissent utiliser Inventory Tweaks avec votre sac à dos ? C’est tout simple :

  • Ajoutez Inventory Tweaks à votre build path
  • Rajoutez l’annotation @ChestContainer au-dessus de votre container comme ceci :
package fr.scarex.tutorialmod.inventory.container;

import invtweaks.api.container.ChestContainer;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.entity.player.InventoryPlayer;
import net.minecraft.inventory.Container;
import net.minecraft.inventory.Slot;
import net.minecraft.item.ItemStack;
import net.minecraft.nbt.NBTTagCompound;
import fr.scarex.tutorialmod.inventory.InventoryBackPack;
import fr.scarex.tutorialmod.inventory.slot.SlotBackPack;
import fr.scarex.tutorialmod.item.ItemBackPack;

/**
    * @author SCAREX
    * 
    * @ChestContainer InventoryTweaks API implementation
    * 

    * isLargeChest indicates whether the buttons should be placed at the top of the gui or on the right.
    */
@ChestContainer(isLargeChest = false)
public class ContainerBackPack extends Container
{
    public InventoryBackPack invBackpack;
    public int rows;

    public ContainerBackPack(InventoryPlayer playerInv, InventoryBackPack inv) {
        this.invBackpack = inv;
        this.rows = inv.getSizeInventory() / 9;
        int i = (this.rows - 4) * 18;
        int j;
        int k;

        // Adding slots to the backpack
        for (j = 0; j < this.rows; ++j) {
            for (k = 0; k < 9; ++k) {
                this.addSlotToContainer(new SlotBackPack(inv, k + j * 9, 8 + k * 18, 18 + j * 18));
            }
        }

        // Adding player's slots
        for (j = 0; j < 3; ++j) {
            for (k = 0; k < 9; ++k) {
                this.addSlotToContainer(new Slot(playerInv, k + j * 9 + 9, 8 + k * 18, 103 + j * 18 + i));
            }
        }

        for (j = 0; j < 9; ++j) {
            this.addSlotToContainer(new Slot(playerInv, j, 8 + j * 18, 161 + i));
        }
    }

    @Override
    public boolean canInteractWith(EntityPlayer player) {
        return true;
    }

    public void writeToNBT(ItemStack stack) {
        if (!stack.hasTagCompound()) stack.setTagCompound(new NBTTagCompound());
        invBackpack.writeToNBT(stack.getTagCompound());
    }

    @Override
    public ItemStack transferStackInSlot(EntityPlayer player, int index) {
        ItemStack itemstack = null;
        Slot slot = (Slot) this.inventorySlots.get(index);

        if (slot != null && slot.getHasStack()) {
            ItemStack itemstack1 = slot.getStack();
            itemstack = itemstack1.copy();

            // Prevents backpack-ception (backpack inside backpack) with
            // shift-click
            if (itemstack.getItem() instanceof ItemBackPack) return null;

            if (index < this.invBackpack.getSizeInventory()) {
                if (!this.mergeItemStack(itemstack1, this.invBackpack.getSizeInventory(), this.inventorySlots.size(), true)) return null;
            } else if (!this.mergeItemStack(itemstack1, 0, this.invBackpack.getSizeInventory(), false)) { return null; }

            if (itemstack1.stackSize == 0)
                slot.putStack((ItemStack) null);
            else
                slot.onSlotChanged();
        }

        return itemstack;
    }

    /**
        * @param buttonPressed
        *            left click, right click, wheel click, etc.
        * @param flag
        *            category (e.g.: hotbar keys)
        */
    @Override
    public ItemStack slotClick(int slotIndex, int buttonPressed, int flag, EntityPlayer player) {
        // Prevents from removing current backpack
        if (flag == 2 && buttonPressed == player.inventory.currentItem) return null;
        if (slotIndex - this.invBackpack.getSizeInventory() - 27 == player.inventory.currentItem) return null;
        return super.slotClick(slotIndex, buttonPressed, flag, player);
    }

    /**
        * Used to save content
        */
    @Override
    public void onContainerClosed(EntityPlayer player) {
        this.writeToNBT(player.getHeldItem());
        super.onContainerClosed(player);
    }
}
  • Modifiez la valeur isLarge à true si vous voulez que les boutons soient placés sur le côté droit.

Résultat

Avec InventoryTweaks :

Voir sur github :

Crédits

Rédaction :

  • SCAREX

Correction :

Textures :

  • Faithful pack (FTB) pour la texture du sac à dos modifiée

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