Ajouter un gui et un container à un bloc
-
Ce tutoriel est également disponible en vidéo.
Sommaire
Introduction
Dans ce tutoriel nous allons apprendre à ajouter un gui et un container à un bloc possédant une entité de bloc. Le résultat final sera un bloc qui fonctionne comme le coffre. Attention ce tutoriel est assez complexe, il y a beaucoup de classes différentes. Nous vous recommandons de bien suivre et d’aller voir le tutoriel en vidéo ou le commit sur github si nécessaire. Soyez également sûr de bien maîtriser tous les pré-requis.
Une bref explication de fonctionnement est nécessaire pour bien comprendre le rôle de chaque classe. Le gui handler est une interface ajoutée par FML qui va nous simplifier le travail. C’est lui qui va indiquer en fonction du mod et du bloc quel gui sera utilisé, et quel container va être utilisé. Il gère également automatiquement le client/serveur. Le gui ne sera que côté client. Il contient tout le code concernant l’affichage (image de fond, texte, etc …) l’affichage des items et des blocs est déjà géré dans GuiContainer, et comme notre gui héritera de cette classe nous n’avons pas besoin de nous en soucier. Tout gui qui hérite de GuiContainer doit avoir un Container associé. C’est lui qui va indiquer au gui quels sont les items présents et donner leurs coordonnées sur l’interface. Le container s’occupe aussi de la synchronisation client/serveur du contenu. En gros il fait le pont entre le tile entity (qui implémentera IInventory) et le gui. Le tile entity sera utilisé pour enregistrer le contenu du bloc à l’aide des fonctions writeToNBT et readFromNBT.
Pré-requis
Code
Modification du bloc :
Nous allons commencer par ajouter la fonction de fml qui gère l’ouverture des gui. Cette fonction doit logiquement être mit dans la fonction onBlockActivated du bloc, puisqu’on veut ouvrir le gui lorsqu’on fait un clic droit sur le bloc :
public boolean onBlockActivated(World world, int x, int y, int z, EntityPlayer player, int side, float hitX, float hitY, float hitZ) { if(world.isRemote) { return true; } else { player.openGui(ModTutoriel.instance, 0, world, x, y, z); return true; } }
La première condition sert à ne rien faire si on se trouve côté client (je rappelle que world.isRemote = client). Sinon si on est côté serveur on appelle la fonction openGui qui se trouve dans EntityPlayer. Cette fonction a comme premier argument l’instance du mod. Je vous avait dit dans le premier tutoriel sur la base du mod que celle-ci allait servir principalement pour ce qui concerne le réseau, nous en avons besoin maintenant. Il suffit donc de mettre ClassePrincipale.instance. Pensez à vérifier que dans votre classe principale votre instance est bien initialisée, il doit y avoir l’annotation @Instance suivis de votre modid, sinon l’instance reste null et vous allez rencontrer un crash avec un NullPointerException (NPE). Si vous avez bien suivis notre tutoriel sur la base du mod vous n’aurais pas de problème.
Le deuxième argument (ici 0) est un id. Cette id est interne à votre mod, il n’y a aucun risque de conflit. Dans notre cas nous n’utiliserons pas cet id, nous allons utiliser le monde et les coordonnées x, y, z pour déterminer quelle action doit être faite dans le gui handler. Par contre dans d’autres cas on peut en avoir besoin. Les quatre derniers arguments sont le monde et les coordonnées x, y, z.Ensuite nous allons avoir besoin de la fonction breakBlock. En effet comme notre bloc pourra contenir d’autres blocs et items, il serait bête de les perdre lorsqu’on détruit notre bloc.
public void breakBlock(World world, int x, int y, int z, Block block, int metadata) { TileEntity tileentity = world.getTileEntity(x, y, z); if(tileentity instanceof IInventory) { IInventory inv = (IInventory)tileentity; for(int i1 = 0; i1 < inv.getSizeInventory(); ++i1) { ItemStack itemstack = inv.getStackInSlot(i1); if(itemstack != null) { float f = world.rand.nextFloat() * 0.8F + 0.1F; float f1 = world.rand.nextFloat() * 0.8F + 0.1F; EntityItem entityitem; for(float f2 = world.rand.nextFloat() * 0.8F + 0.1F; itemstack.stackSize > 0; world.spawnEntityInWorld(entityitem)) { int j1 = world.rand.nextInt(21) + 10; if(j1 > itemstack.stackSize) { j1 = itemstack.stackSize; } itemstack.stackSize -= j1; entityitem = new EntityItem(world, (double)((float)x + f), (double)((float)y + f1), (double)((float)z + f2), new ItemStack(itemstack.getItem(), j1, itemstack.getItemDamage())); float f3 = 0.05F; entityitem.motionX = (double)((float)world.rand.nextGaussian() * f3); entityitem.motionY = (double)((float)world.rand.nextGaussian() * f3 + 0.2F); entityitem.motionZ = (double)((float)world.rand.nextGaussian() * f3); if(itemstack.hasTagCompound()) { entityitem.getEntityItem().setTagCompound((NBTTagCompound)itemstack.getTagCompound().copy()); } } } } world.func_147453_f(x, y, z, block); } super.breakBlock(world, x, y, z, block, metadata); }
Ce code sert donc à dropper tout le contenu de notre bloc lorsqu’il est détruit. Dans un premier temps on obtient l’instance de l’entité de bloc qui se trouve aux coordonnées du bloc. Si celle-ci implémente l’interface IInventory, on fait avoir une boucle for pour parcourir tout son contenu et on créé à chaque fois un EntityItem pour chaque ItemStack (voir plusieurs EntityItems pour un ItemStack qui sera découpé en fonction du random) qu’on fait apparaître dans le monde.
Une dernière fonction optionnelle, qui aura pour but de mettre un nom différent à nombre gui si le bloc a été renommé avec une enclume avant d’être posé :
public void onBlockPlacedBy(World world, int x, int y, int z, EntityLivingBase living, ItemStack stack) { TileEntity tile = world.getTileEntity(x, y, z); if(tile instanceof TileEntityTutoriel) { if(stack.hasDisplayName()) { ((TileEntityTutoriel)tile).setCustomName(stack.getDisplayName()); } } }
La fonction setCustomName de votre TileEntity n’existant pas encore, vous allez avoir une erreur dessus. Nous allons donc bientôt la créer.
Cette fonction n’étant pas compliquée je ne pense pas qu’il soit nécessaire de l’expliquer.Modification du tile entity :
Comme vous devrez l’avoir vu dans les tutoriels pré-requis, les entités de bloc permettent d’enregistrer des valeurs dans le monde qui sont uniques à chaque bloc du monde. Le contenu de notre bloc sera donc enregistré ici. Nous allons commencer par implémenter l’interface IInventory qui est nécessaire pour tous les inventaires. Après la déclaration de votre classe ajoutez donc “implements IInventory” :
public class TileEntityTutoriel extends TileEntity implements IInventory
Passez la souris sur l’erreur affichée sur le nom de votre TileEntity et choisissez “add unimplemented methods”. Cela va ajouter un paquet de méthodes que nous modifierons par la suite.
Mais d’abord nous allons déclarer deux variables, un tableau d’ItemStack qui va correspondre au contenu et un String qui va correspondre au nom custom :private ItemStack[] contents = new ItemStack[27]; private String customName;
27 est le nombre de stacks que pourra contenir mon bloc, cela fait 9x3 soit la taille d’un coffre simple.
Nous allons maintenant ajouter les fonctions pour enregistrer le contenu dans le tag nbt :@Override public void readFromNBT(NBTTagCompound compound) { super.readFromNBT(compound); // exécute ce qui se trouve dans la fonction readFromNBT de la classe mère (lecture de la position du tile entity) if(compound.hasKey("CustomName", Constants.NBT.TAG_STRING)) // si un tag custom name de type string existe { this.customName = compound.getString("CustomName"); // on le lit } NBTTagList nbttaglist = compound.getTagList("Items", Constants.NBT.TAG_COMPOUND); // on obtient la liste de tags nommée Items this.contents = new ItemStack[this.getSizeInventory()]; // on réinitialise le tableau for(int i = 0; i < nbttaglist.tagCount(); ++i) // i varie de 0 à la taille la liste { NBTTagCompound nbttagcompound1 = nbttaglist.getCompoundTagAt(i); // on lit le tag nbt int j = nbttagcompound1.getByte("Slot") & 255; // on lit à quel slot se trouve l'item stack if(j >= 0 && j < this.contents.length) { this.contents[j] = ItemStack.loadItemStackFromNBT(nbttagcompound1); // on lit l'item stack qui se trouve dans le tag } } } @Override public void writeToNBT(NBTTagCompound compound) { super.writeToNBT(compound); // exécute se qui se trouve dans la fonction writeToNBT de la classe mère (écriture de la position du tile entity) if(this.hasCustomInventoryName()) // s'il y a un nom custom { compound.setString("CustomName", this.customName); // on le met dans le tag nbt } NBTTagList nbttaglist = new NBTTagList(); // on créé une nouvelle liste de tags for(int i = 0; i < this.contents.length; ++i) // i varie de 0 à la taille de notre tableau { if(this.contents[ i] != null) // si l'item stack à l'emplacement i du tableau n'est pas null { NBTTagCompound nbttagcompound1 = new NBTTagCompound(); // on créé un tag nbt nbttagcompound1.setByte("Slot", (byte)i); // on enregistre son emplacement dans le tableau this.contents[ i].writeToNBT(nbttagcompound1); // on écrit l'item dans le tag nbttaglist.appendTag(nbttagcompound1); // on ajoute le tab à la liste } } compound.setTag("Items", nbttaglist); // on enregistre la liste dans le tag nbt }
Maintenant nous allons nous occuper de modifier les fonctions que nous avons implémentées avant.
Dans la fonction getSizeInventory nous allons mettre return this.contents.length; :@Override public int getSizeInventory() { return this.contents.length; }
Cette fonction doit renvoyer la taille de l’inventaire donc on met la taille du tableau (this.contents.length).
La fonction getStackInSlot(int) doit renvoyer l’itemstack qui se trouve dans l’emplacement qui est en argument. Pour une question de lisibilité du code je vous conseil de mettre un nom d’argument plus clair que celui mit par défaut par Eclipse, slotIndex conviendra très bien :
@Override public ItemStack getStackInSlot(int slotIndex) { return this.contents[slotIndex]; }
La fonction decrStackSize est utilisé pour retire un item du contenu. Modifiez-la afin soit comme ceci :
@Override public ItemStack decrStackSize(int slotIndex, int amount) { if(this.contents[slotIndex] != null) // si le contenu dans l'emplacement n'est pas null { ItemStack itemstack; if(this.contents[slotIndex].stackSize <= amount) // si la quantité est inférieur où égale à ce qu'on souhaite retirer { itemstack = this.contents[slotIndex]; // la variable itemstack prends la valeur du contenu this.contents[slotIndex] = null; // on retire ce qui est dans la variable contents this.markDirty(); // met à jour le tile entity return itemstack; // renvoie itemstack } else // sinon { itemstack = this.contents[slotIndex].splitStack(amount); // la fonction splitStack(quantité) retire dans this.contents[slotIndex] le contenu et le met dans itemstack if(this.contents[slotIndex].stackSize == 0) // au cas où la quantité passe à 0 (ce qui ne devrait pas arriver en temps normal) { this.contents[slotIndex] = null; // on met sur null, ça évite de se retrouver avec des itemstack bugué qui contiennent 0 } this.markDirty(); // met à jour le tile entity return itemstack; // renvoie itemstack } } else // sinon si le contenu dans cette emplacement est null { return null; // renvoie null, puisqu'il n'y a rien dans cette emplacement } }
Le code est directement commenté avec des commentaires Java, vu sa longueur c’est plus simple d’expliquer comme ceci.
Ensuite la fonction getStackInSlotOnClosing :
@Override public ItemStack getStackInSlotOnClosing(int slotIndex) { if(this.contents[slotIndex] != null) { ItemStack itemstack = this.contents[slotIndex]; this.contents[slotIndex] = null; return itemstack; } else { return null; } }
Elle fonctionne presque comme decrStackSize mais cette fois-ci il n’y a pas la quantité en argument car on récupère tout l’item stack. Elle est utilisée pour le shift + clic.
La fonction setInventorySlotContents permet de mettre un item stack dans un emplacement défini. Modifiez-la comme ceci :
@Override public void setInventorySlotContents(int slotIndex, ItemStack stack) { this.contents[slotIndex] = stack; // met l'item stack dans le tableau if(stack != null && stack.stackSize > this.getInventoryStackLimit()) // si la taille de l'item stack dépasse la limite maximum de l'inventaire { stack.stackSize = this.getInventoryStackLimit(); // on le remet sur la limite } this.markDirty(); // met à jour le tile entity }
Une fois de plus j’ai directement commenté le code pour l’expliquer.
getInventoryName permet d’avoir le nom de l’inventaire. Il sera utilisé dans le gui. Ajoutez “this.hasCustomInventoryName() ? this.customName : “tile.cupboard”;” dans le return :
@Override public String getInventoryName() { return this.hasCustomInventoryName() ? this.customName : "tile.cupboard"; }
Si l’inventaire à un nom custom on renvoie customName (variable que nous avons créé au début) sinon on renvoie “tile.cupboard” qui va correspondre au nom non localisé de mon inventaire. (cupboard pour placard dans mon cas puisque dans ce tutoriel je fais un petit placard, bien sûr à vous de mettre ce que vous souhaitez ici). Pensez à ajouter ce qu’il faut dans le fichier lang, dans mon cas ça sera tile.cupboard=Cupboard pour le fichier en_US.lang et tile.cupboard=Placard pour le fichier fr_FR.lang.
Avant de nous occuper des autres fonctions nous allons créer une nouvelle fonction nommée setCustomName :
public void setCustomName(String customName) { this.customName = customName; }
La fonction n’a rien de compliqué, c’est juste un setter pour le nom custom. Cela devrait corriger l’erreur que vous aviez dans la classe du bloc.
La fonction getInventoryStackLimit détermine la valeur maximum que peut contenir un emplacement, il faut donc mettre 64 :
@Override public int getInventoryStackLimit() { return 64; }
Vous pouvez mettre moins si vous le souhaitez, par contre il n’est pas possible de mettre plus (vous pouvez mettre 128 si ça vous amuse mais il ne sera pas possible de mettre 2 stacks dans un emplacement, pour ça il faudrait modifier en profondeur Minecraft ce qui n’est pas envisageable).
isUseableByPlayer détermine si un joueur peut utiliser cet inventaire. Modifiez-la comme ceci :
@Override public boolean isUseableByPlayer(EntityPlayer player) { return this.worldObj.getTileEntity(this.xCoord, this.yCoord, this.zCoord) != this ? false : player.getDistanceSq((double)this.xCoord + 0.5D, (double)this.yCoord + 0.5D, (double)this.zCoord + 0.5D) <= 64.0D; }
La première partie de la condition vérifie que le tile entity aux coordonnées this.xCoord, this.yCoord, this.zCoord est bien ce tile entity. En théorie cela devrait toujours être vrai, mais en pratique des problèmes peuvent arriver et donc cette condition évite des possibles crashs ou corruption de map. La deuxième partie vérifie que la distance au cube entre le centre de l’entity de bloc et le joueur est inférieur à 64 et comme ∛64 = 4, cela vérifie que le joueur est à moins de 4 blocs de l’entity de bloc.
Concernant les fonctions openInventory et closeInventory laissez-les vides, elles ne nous servirons pas. (elles sont seulement utiles dans le cas où l’on veut faire une animation d’ouverture/fermeture, donc nous les utiliserons dans un autre tutoriel).
La fonction isItemValidForSlot est utilisée pour les entonnoirs. Dans le cas d’un bloc qui permet de stocker il suffit de mettre le renvoie sur true. Dans le cas d’un bloc comme le four, on vérifiera par exemple que le bloc est un carburant si l’emplacement est celui du bas. Dans notre cas on mettra donc :
@Override public boolean isItemValidForSlot(int slotIndex, ItemStack stack) { return true; }
Création et enregistrement du gui handler :
Le gui handler est une classe qui va gérer les guis et les containers. Elle est liée à la fonction openGui du joueur. Il ne doit avoir qu’un seul gui handler par mod. Pour le repérer fml passe par l’instance du mod, il est donc essentiel que votre instance soit bien déclarée. Si vous n’avez pas suivi notre tutoriel sur la classe principale, vérifiez que vous avez ceci dans votre classe principale :
@Instance("votre modid") // attention il doit respecter les majuscules/minuscules public static ModTutoriel instance;
où ModTutoriel est le nom de ma classe principale. Il ne doit rien avoir non plus entre ces deux lignes, elles doivent se suivre.
Ensuite dans la fonction init enregistrez votre gui handler :
NetworkRegistry.INSTANCE.registerGuiHandler(instance, new GuiHandlerTuto());
GuiHandlerTuto est le nom de ma classe, vous pouvez bien sûr la nommer autrement. Ensuite créez-la.
Normalement Eclipse devrait automatiquement mettre implements IGuiHandler et implémentez les méthodes qui vont avec. Si ce n’est pas le cas faites-le manuellement. Vous devrez avoir maintenant quelque chose comme ceci :
package fr.minecraftforgefrance.tutoriel.common; import net.minecraft.entity.player.EntityPlayer; import net.minecraft.tileentity.TileEntity; import net.minecraft.world.World; import cpw.mods.fml.common.network.IGuiHandler; import fr.minecraftforgefrance.tutoriel.client.GuiCupboard; public class GuiHandlerTuto implements IGuiHandler { @Override public Object getServerGuiElement(int ID, EntityPlayer player, World world, int x, int y, int z) { return null; } @Override public Object getClientGuiElement(int ID, EntityPlayer player, World world, int x, int y, int z) { return null; } }
La fonction getServerGuiElement devra renvoyer le container et la fonction getClientGuiElement devrait renvoyer le gui. Dans le constructeur du gui le container indiqué. Le gui est donc que côté client alors que le container est commun au client et au serveur.
Nous retrouvons en argument id, player, world et les coordonnées. Ce sont presque les mêmes arguments que la fonction openGui du joueur. (player.openGui(instance, id, world, x, y, z);).
Comme je l’ai déjà dit au début du tutoriel nous n’allons pas utiliser l’id, en revanche il peut être utile dans d’autres cas (ajoute d’un container et d’un gui sur un item stack ou sur une entité).
Les blocs étant fixes nous allons nous servir des coordonnées et du tile entity.
Dans la fonction getServerGuiElement ajoutez ceci :TileEntity tile = world.getTileEntity(x, y, z); if(tile instanceof TileEntityTutoriel) { return new ContainerCupboard((TileEntityTutoriel)tile, player.inventory); } return null;
TileEntityTutoriel est mon tile entity comme vu plutôt et ContainerCupboard va être mon container. Si vous souhaitez ajouter d’autres containers et guis dans votre mod, il faudrait mettre des else if :
TileEntity tile = world.getTileEntity(x, y, z); if(tile instanceof TileEntityTutoriel) { return new ContainerCupboard((TileEntityTutoriel)tile, player.inventory); } else if(tile instanceof UnAutreTileEntity) { return new UnAutreContainer((UnAutreTileEntity)tile, player.inventory); } else if(ID == un id) { return new ContainerPourItem(player.getCurrentItem(), player.inventory); // sera traité dans un autre tutoriel } return null;
Ensuite on fait la même chose pour getClientGuiElement mais avec un gui cette fois :
TileEntity tile = world.getTileEntity(x, y, z); if(tile instanceof TileEntityTutoriel) { return new GuiCupboard((TileEntityTutoriel)tile, player.inventory); } return null;
Pour l’instant vous allez avoir des erreurs sur new VotreGui(…) et new VotreContainer(…), elles se corrigerons par la suite.
Création du container :
Le container va s’occuper de synchroniser automatiquement le contenu des emplacements. Il va également définir ou ces emplacements se trouvent.
Commencez par créer une classe (dans mon cas ContainerCupboard) extends Container et implémentez la méthode abstraite (après avoir ajouté l’extends et importé net.minecraft.inventory.Container passez la souris sur l’erreur au niveau du nom de la classe puis faites “add unimplemented methods”).
Ensuite nous allons lui ajouter un constructeur avec comme arguments le tile entity et l’inventaire du joueur et une variable qui va correspondre à l’instance du tile entity :public class ContainerCupboard extends Container { private final TileEntityTutoriel tileTuto; public ContainerCupboard(TileEntityTutoriel tile, InventoryPlayer inventory) { this.tileTuto = tile; // nous allons ajouter la suite ici } @Override public boolean canInteractWith(EntityPlayer player) { // TODO Auto-generated method stub return false; } }
Maintenant, toujours dans le constructeur (là où j’ai mit le commentaire) nous allons ajouter ceci :
tile.openInventory(); for(int i = 0; i < 3; ++i) { for(int j = 0; j < 9; ++j) { this.addSlotToContainer(new Slot(tile, j + i * 9, 8 + j * 18, 18 + i * 18)); } } this.bindPlayerInventory(inventory);
La première ligne appel la fonction openInventory qui se trouve dans le gui. Nous n’allons pas nous en servir tout de suite, mais elle sera utile par la suite pour animer le bloc. Si vous ne souhaitez pas animer votre bloc par la suite cette ligne n’est pas nécessaire.
Ensuite j’ai mis deux boucles for, une qui va de 0 (inclut) à 3 (exclut) et dans celle-ci une autre qui va de 0 (inclut) à 9 (exclut). Ainsi j + i * 9 va varier de 0 (inclut) à 27 (exclut).
La classe Slot à pour constructeur new Slot(inventaire lié, id du slot, coordonnée x, coordonnée y). Dans mon cas l’id est donc j + i * 9 et comme dit plus haut cette valeur varie de 0 (inclut) à 27 (exclut). Et si vous avez bien suivi le tutoriel vous avez remarqué que le tableau d’item stack dans mon tile entity a pour taille 27 (donc de 0 inclut à 27 exclut). Ce n’est pas un hasard, c’est bien sûr voulu. Vous ne devez jamais dépasser la taille du tableau dans vos slots, sinon vous allez avoir un ArrayIndexOutOfBoundsException. Si vous ajoutez deux fois un slot avec le même id mais avec des coordonnées différentes le slot sera identique. Donc lorsque vous allez ajouter du contenu dans l’un il sera aussi dans l’autre et lorsque vous allez le retirer d’un il va aussi s’enlever de l’autre.
Si vous voulez changer l’emplacement des slots je vous conseille d’attendre d’avoir fini le tutoriel afin de ne plus avoir d’erreur et de pouvoir lancer le jeu. Ensuite lancez le jeu en debug (le scarabée vert à gauche de la flèche pour lancer normalement) comme ça vous pourrez voir les changements en direct. Vous aurez juste à changer une valeur pour ouvrir à nouveau le container pour voir où se retrouve l’emplacement sans relancer le jeu à chaque fois.
La dernière ligne appelle la fonction bindPlayerInventory qui … n’existe pas ! Nous allons donc l’ajouter :private void bindPlayerInventory(InventoryPlayer inventory) { int i; for(i = 0; i < 3; ++i) { for(int j = 0; j < 9; ++j) { this.addSlotToContainer(new Slot(inventory, j + i * 9 + 9, 8 + j * 18, 86 + i * 18)); } } for(i = 0; i < 9; ++i) { this.addSlotToContainer(new Slot(inventory, i, 8 + i * 18, 144)); } }
Même principe qu’avant, on utilise des boucles for pour éviter d’écrire 36 fois la même ligne. Les deux premières boucles afficher le haut de l’inventaire et la troisième boucle affiche le bas de l’inventaire.
Ensuite nous allons ajouter la fonction transferStackInSlot nécessaire pour le bon fonctionnement du shift-clic :
public ItemStack transferStackInSlot(EntityPlayer player, int slotIndex) { ItemStack itemstack = null; Slot slot = (Slot)this.inventorySlots.get(slotIndex); if(slot != null && slot.getHasStack()) { ItemStack itemstack1 = slot.getStack(); itemstack = itemstack1.copy(); if(slotIndex < this.tileTuto.getSizeInventory()) { if(!this.mergeItemStack(itemstack1, this.tileTuto.getSizeInventory(), this.inventorySlots.size(), true)) { return null; } } else if(!this.mergeItemStack(itemstack1, 0, this.tileTuto.getSizeInventory(), false)) { return null; } if(itemstack1.stackSize == 0) { slot.putStack((ItemStack)null); } else { slot.onSlotChanged(); } } return itemstack; }
Avant dernière ou dernière étape, il faut changer le renvoie de la fonction canInteractWith. On va lui dire d’utiliser la fonction isUseableByPlayer qui se trouve dans le tile entity :
@Override public boolean canInteractWith(EntityPlayer player) { return this.tileTuto.isUseableByPlayer(player); }
Et pour finir, pour ceux qui voudront animer la fermeture du bloc plus tard il faut ajouter cette fonction :
public void onContainerClosed(EntityPlayer player) { super.onContainerClosed(player); this.tileTuto.closeInventory(); }
Création du gui :
Dernière étape du tutoriel, la création du gui. Le gui (pour graphical user interface) est l’interface graphique, donc la partie visuelle. C’est pour ça qu’elle n’est que nécessaire côté client.
Créez la classe (dans mon cas GuiCupboard) en mettant comme classe mère net.minecraft.client.gui.inventory.GuiContainer (ou ajoutez extends GuiContainer une fois créé et importez net.minecraft.client.gui.inventory.GuiContainer).
Modifiez la classe tel qu’elle soit comme ceci :public class GuiCupboard extends GuiContainer { private static final ResourceLocation textures = new ResourceLocation(ModTutoriel.MODID, "textures/gui/container/cupboard.png"); private TileEntityTutoriel tileTuto; private IInventory playerInv; public GuiCupboard(TileEntityTutoriel tile, InventoryPlayer inventory) { super(new ContainerCupboard(tile, inventory)); this.tileTuto = tile; this.playerInv = inventory; this.allowUserInput = false; this.ySize = 170; } @Override protected void drawGuiContainerBackgroundLayer(float partialRenderTick, int x, int y) { // TODO Auto-generated method stub } }
Comme vous pouvez le voir j’ai ajouté quelques variables et un constructeur. La première variable correspond à la texture, le constructeur de ResourceLocation est le suivant :
ResourceLocation(modid, “chemin exacte de la texture à partir du dossier assets/modid”)
Vous pouvez aussi utiliser celui-ci :
ResourceLocation(“modid:chemin exacte de la texture à partir du dossier assets/modid”)
Dans mon cas la texture sera dans le dossier forge/src/main/resources/assets/modtutoriel/textures/gui/container/ et la texture sera nommée cupboard et aura l’extension .png. Elle ressemble à ceci : https://raw.githubusercontent.com/FFMT/ModTutoriel17/2a71e097ba9d5d59719aaedccdb0445589773e22/src/main/resources/assets/modtutoriel/textures/gui/container/cupboard.png (le blanc est de la transparence). Elle fait 256x256, il est important de respecter un ratio qui est multiple de 2 (256x256, 512x256, 512x512, etc …).Ensuite expliquons le constructeur. La ligne super(new ContainerCupboard(tile, inventory)); indique le container, il est nécessaire car le code qui se trouve dans GuiContainer va automatiquement afficher les slots avec leur contenu. Les deux lignes qui suivent initialisent les variables qu’on a ajoutées. Ces variables vont servir pour afficher le nom de l’inventaire. Ensuite on met allowUserInput sur false puisque l’utilisateur n’aura pas besoin d’écrire (pas de texte box dans ce gui). La ligne ySize = 170 défini la taille y du gui sur 170, ce qui correspond à deux pixels de plus que la hauteur de ma texture. La valeur par défaut de xSize correspond déjà à ce dont j’ai besoin. En fonction de la taille de votre texture vous aurez besoin de modifier ces deux valeurs.
Maintenant nous allons ajouter une nouvelle fonction pour afficher les noms des inventaires :
protected void drawGuiContainerForegroundLayer(int x, int y) { String tileName = this.tileTuto.hasCustomInventoryName() ? this.tileTuto.getInventoryName() : I18n.format(this.tileTuto.getInventoryName()); this.fontRendererObj.drawString(tileName, (this.xSize - this.fontRendererObj.getStringWidth(tileName)) / 2, 6, 0); String invName = this.playerInv.hasCustomInventoryName() ? this.playerInv.getInventoryName() : I18n.format(this.playerInv.getInventoryName()); this.fontRendererObj.drawString(invName, (this.xSize - this.fontRendererObj.getStringWidth(invName)) / 2, this.ySize - 96, 0); }
On retrouve deux fois la même chose mais avec des coordonnées différentes, la première fois c’est le nom de l’inventaire du bloc et la second fois c’est celui du joueur.
On vérifie d’abord que l’inventaire à un nom custom, si c’est le cas on prend directement ce nom sinon on passe par I18n.format qui va chercher le nom dans le fichier lang.
La fonction drawString a pour argument(texte à afficher, x, y, couleur).
Vous pouvez voir que j’ai utilisé (this.xSize - this.fontRendererObj.getStringWidth(invName)) / 2 comme coordonnée x, cela permet de centrer le texte. Pour la couleur j’ai mis 0, ce qui correspond au noir. Vous pouvez utiliser les codes couleurs html en le mettant une valeur héxadécimale (0x000000 pour le noir, 0xFFFFFF pour le blanc, 0xFF0000 pour le rouge, 0x00FF00 pour le vert et 0x0000FF pour le bleu. Il existe pleins de sites/programmes avec un sélecteur de couleur qui vous donne le code héxadécimal de la couleur, en voici un : http://www.proftnj.com/RGB3.htm)Pour finir nous allons modifier la fonction drawGuiContainerBackgroundLayer :
@Override protected void drawGuiContainerBackgroundLayer(float partialRenderTick, int x, int y) { GL11.glColor4f(1.0F, 1.0F, 1.0F, 1.0F); // on colorise this.mc.getTextureManager().bindTexture(textures); // affiche la texture int k = (this.width - this.xSize) / 2; // on calcul la coordonnée x du point en haut à gauche int l = (this.height - this.ySize) / 2; // on calcul la coordonnée y du point en haut à gauche this.drawTexturedModalRect(k, l, 0, 0, this.xSize, this.ySize); // on desine la texture, la fonction à pour argument point x de départ, point y de départ, vecteur u, vecteur v, largeur, hauteur) }
Et voila, votre gui est terminé ! Normalement toutes les erreurs devraient être corrigés, il ne vous reste pluqu’à tester.
Résultat
Voir le commit sur github
Le commit sur github montre clairement où ont été placés les fichiers, ainsi que ce qui a été ajouté et retiré dans le fichier.En vidéo
https://www.youtube.com/watch?v=CHw5ri-QU6o
Crédits
Rédaction :
Correction :
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 -
Bonjour,
Avant tout merci pour ce Tuto encore très bien Rédige !
Après l’avoir suivi à la lettre, tout fonctionne parfaitement !!J’ai modifié à ma guise à savoir :
Le conteneur a été réduit à 2 slot.Et que à l’activation rédstone du block, il l lance une boucle pour remplacer l’air sur 5 block au dessus de lui par de la stone.
Jusque là tout marche impeccable !!Mais j’aurais aimé que le setblock soit fait avec le block présent dans le stack(0) de mon conteneur…
Je me doute que ça soit suffisamment bête pour que je ne le trouve pas… Mais je bute…
J’ai bataille tout la mâtiné pour arriver à ça… Sans succès…
Donc ma ptite question est :Est-il possible d’aller chercher le contenu du stack(0)
Pour s’en servir dans ma fonction de génération ?
Si oui je veux bien un ptit coup de pouce,
J’ai essayé diverses choses ce matin trop longue a toute écrire,
getstackinslot[0] etc etc.
Mais je coince a chaque fois (sûrement car je sais pas trop comment importer ca dans ma classe du block…)Si il faut ce soir j’ouvrirai un autre sujet.
Dans la section support.
Bonne journée. -
il suffit de tester que getStackInSlot(0) != null et que Block.getBlockFromItem(getStackInSlot(0)) != null.
Ensuite tu utilises Block.getBlockFromItem(getStackInSlot(0)) pour ton setBlock -
@‘robin4002’:
il suffit de tester que getStackInSlot(0) != null et que Block.getBlockFromItem(getStackInSlot(0)) != null.
Ensuite tu utilises Block.getBlockFromItem(getStackInSlot(0)) pour ton setBlocksalut,
je te remercie je vais tester ça.a +
-
Super tutoriel !
Une simple question : à quoi sert la fonction “world.func_147453_f(x, y, z, block)” dans le onBreak() du Block ? -
elle sert à signalé que le bloc a changé.
-
Merci ! Et une autre remarque , dans le onBlockActivated() , tu ouvres le gui quand isRemote vaut false , ce n’est pas le contraire ?
-
Non c’est bien si il est false, car le serveur va ouvrir le Container du gui puis enverra un packet au client pour lui dire d’ouvrir le gui.
-
Euh… J’aimerais savoir comment on fait pour créer une sorte de nouvelle table de craft ? Pour m’aider, j’ai un peu observé le pré-code qu’ils utilisent sur MCreator mais bon…
-
https://www.minecraftforgefrance.fr/showthread.php?tid=3556
Un peu de recherche ne fait pas de mal. -
O.K, merci, je suis désolé de demander comme ça, hein mais je ne trouvais pas dans le sommaire et j’oublie tt le temps de chercher un article particulier sur le site
EDIT : J’ai lu l’article et les commentaires mais j’avoue que ça m’intéresserait plus en 1.7.10 comme tuto complet si tu en as un
-
N’empêche que c’est vrai que ça serait bien de l’ajouter au sommaire ^^ mais c’est pas moi qui le gère.
-
Faut regarder du côté des TileEntities
-
O.k merci mais ou précisément ?
-
Salut,
J’ai une petite erreur au niveau de la TileEntity
http://prntscr.com/d6eakwCordialement, FanatikForce
-
Cette fonction doit être mise dans la classe du bloc et non dans le tile entity.
-
J’ai un problème quant je mes un hopper au dessus de mon block, le contenue du hopper disparaît bien mais il apparaît pas tous dans mon block et quant mon block est full il disparaisse comme même du hopper.
PS le block a un seul emplacement qui peut contenir qu’un item.
-
Pourrais-tu montrer la classe de ton TileEntity ?
-
Merci pour le tuto !!
J’ai un petit soucis,
Si je passe le getInventoryStackLimit() à 1 pour avoir un seul item, quand on utilise le shift + click sur un stack de plusieurs items, ils disparaissent (à l’exception de celui qui rentre dans le slot)
La fonction transferStackInSlot n’est pas commenté, du coup je suis un peu perdu
Les sources :
https://github.com/mandonnaud/houbmod/blob/master/src/main/java/fr/adslhouba/houbmod/common/ContainerCupboard.java
https://github.com/mandonnaud/houbmod/blob/master/src/main/java/fr/adslhouba/houbmod/common/block/cc/TileEntityEnclumeCPO.java -
Bonjour.
J’ai suivi ce tutoriel à la lettre et j’ai voulu tester si ça marcher bien et quand je clique sur mon block… BOOM… MineCraft Crash !!! et je sais pas pourquoi…
Voici mes code :
Classe Principale :
package mod.plantsandfoodpack.common; import net.minecraft.block.Block; import net.minecraft.block.Block.SoundType; import net.minecraft.block.material.Material; import net.minecraft.creativetab.CreativeTabs; import net.minecraft.init.Blocks; import net.minecraft.init.Items; import net.minecraft.item.Item; import net.minecraft.item.ItemFood; import net.minecraft.item.ItemSeeds; import net.minecraft.item.ItemStack; import mod.plantsandfoodpack.proxy.CommonProxy; import cpw.mods.fml.common.Mod; import cpw.mods.fml.common.Mod.EventHandler; import cpw.mods.fml.common.Mod.Instance; import cpw.mods.fml.common.SidedProxy; import cpw.mods.fml.common.event.FMLInitializationEvent; import cpw.mods.fml.common.event.FMLPostInitializationEvent; import cpw.mods.fml.common.event.FMLPreInitializationEvent; import cpw.mods.fml.common.network.NetworkRegistry; import cpw.mods.fml.common.registry.GameRegistry; @Mod(modid= "plantsandfoodpack", name="Plants & Food Pack Mod", version ="1.0.0") public class ModPlantsandFoodPack{ public static final String MODID = "plantsandfoodpack"; @Instance(MODID) public static ModPlantsandFoodPack instance; @SidedProxy(clientSide = "mod.plantsandfoodpack.proxy.ClientProxy", serverSide ="mod.plantsandfoodpack.proxy.CommonProxy") public static CommonProxy proxy; public static Item flour, itemTomato, itemTomatoSeeds, itemLettuceSeeds, itemLettuceLeaf; public static Block blockSugar, blockTomatoCrop, blockLettuceCrop, blockKitchenCupboard; @EventHandler public void preInit(FMLPreInitializationEvent event){ blockSugar = new BlockSugar(Material.sand).setBlockName("sugarBlock").setBlockTextureName(MODID + ":sugar_block").setCreativeTab(CreativeTabs.tabBlock).setStepSound(Block.soundTypeSand); blockTomatoCrop = new BlockTomatoCrop().setBlockName("tomatoCrop").setBlockTextureName(MODID + ":tomato_crop"); blockLettuceCrop = new BlockLettuceCrop().setBlockName("lettuceCrop").setBlockTextureName(MODID + ":lettuce_crop"); blockKitchenCupboard = new BlockKitchenCupboard(Material.wood).setBlockName("kitchenCupboard"); flour = new ItemFlour().setUnlocalizedName("flour").setTextureName(MODID + ":flour").setCreativeTab(CreativeTabs.tabMaterials); itemTomato = new ItemTomato(3, 2.5F, false).setUnlocalizedName("tomato").setTextureName(MODID + ":tomato").setCreativeTab(CreativeTabs.tabFood); itemTomatoSeeds = new ItemSeeds(this.blockTomatoCrop, Blocks.farmland).setUnlocalizedName("tomatoSeed").setTextureName(MODID + ":tomato_seeds").setCreativeTab(CreativeTabs.tabMaterials); itemLettuceLeaf = new ItemFood(2, 1.5F, false).setUnlocalizedName("lettuceLeaf").setTextureName(MODID + ":lettuce_leaf").setCreativeTab(CreativeTabs.tabFood); itemLettuceSeeds = new ItemSeeds(this.blockLettuceCrop, Blocks.farmland).setUnlocalizedName("lettuceSeeds").setTextureName(MODID + ":lettuce_seeds").setCreativeTab(CreativeTabs.tabMaterials); GameRegistry.registerBlock(blockSugar, "sugar_block"); GameRegistry.registerBlock(blockTomatoCrop, "tomatoCrop"); GameRegistry.registerBlock(blockLettuceCrop, "lettuceCrop"); GameRegistry.registerBlock(blockKitchenCupboard, "kitchenCupboard"); GameRegistry.registerItem(flour, "flour"); GameRegistry.registerItem(itemTomato, "tomato"); GameRegistry.registerItem(itemTomatoSeeds, "tomato_seeds"); GameRegistry.registerItem(itemLettuceLeaf, "lettuce_leaf"); GameRegistry.registerItem(itemLettuceSeeds, "lettuce_seeds"); } @EventHandler public void Init(FMLInitializationEvent event){ GameRegistry.addRecipe(new ItemStack(flour, 1), new Object[] {"*", '*', Items.wheat}); GameRegistry.addRecipe(new ItemStack(blockSugar, 1), new Object[] {"###", "###", "###", '#', Items.sugar}); GameRegistry.addRecipe(new ItemStack(blockKitchenCupboard, 1), new Object[] {"WWW", "W W", "WWW", 'W', Blocks.log}); GameRegistry.registerTileEntity(TileEntityKitchenCupboard.class, MODID + ":kitchenCupboard"); proxy.registerRender(); NetworkRegistry.INSTANCE.registerGuiHandler(instance, new GuiHandlerPlantsAndFoodPack()); } @EventHandler public void postInit(FMLPostInitializationEvent event){ } }
Classe du Block :
package mod.plantsandfoodpack.common; import mod.plantsandfoodpack.proxy.ClientProxy; import net.minecraft.block.Block; import net.minecraft.block.material.Material; import net.minecraft.entity.EntityLivingBase; import net.minecraft.entity.item.EntityItem; import net.minecraft.entity.player.EntityPlayer; import net.minecraft.inventory.IInventory; import net.minecraft.item.ItemStack; import net.minecraft.nbt.NBTTagCompound; import net.minecraft.tileentity.TileEntity; import net.minecraft.tileentity.TileEntityChest; import net.minecraft.util.MathHelper; import net.minecraft.world.World; import net.minecraftforge.common.util.ForgeDirection; public class BlockKitchenCupboard extends Block{ protected BlockKitchenCupboard(Material p_i45394_1_) { super(p_i45394_1_); } @Override public boolean hasTileEntity(int metadata) { return true; } @Override public TileEntity createTileEntity(World world, int metadata) { return new TileEntityKitchenCupboard (); } public void breakBlock(World world, int x, int y, int z, Block block, int metadata) { TileEntity tileentity = world.getTileEntity(x, y, z); if (tileentity instanceof IInventory) { IInventory inv = (IInventory)tileentity; for (int i1 = 0; i1 < inv.getSizeInventory(); ++i1) { ItemStack itemstack = inv.getStackInSlot(i1); if (itemstack != null) { float f = world.rand.nextFloat() * 0.8F + 0.1F; float f1 = world.rand.nextFloat() * 0.8F + 0.1F; EntityItem entityitem; for (float f2 = world.rand.nextFloat() * 0.8F + 0.1F; itemstack.stackSize > 0; world.spawnEntityInWorld(entityitem)) { int j1 = world.rand.nextInt(21) + 10; if (j1 > itemstack.stackSize) { j1 = itemstack.stackSize; } itemstack.stackSize -= j1; entityitem = new EntityItem(world, (double)((float)x + f), (double)((float)y + f1), (double)((float)z + f2), new ItemStack(itemstack.getItem(), j1, itemstack.getItemDamage())); float f3 = 0.05F; entityitem.motionX = (double)((float)world.rand.nextGaussian() * f3); entityitem.motionY = (double)((float)world.rand.nextGaussian() * f3 + 0.2F); entityitem.motionZ = (double)((float)world.rand.nextGaussian() * f3); if (itemstack.hasTagCompound()) { entityitem.getEntityItem().setTagCompound((NBTTagCompound)itemstack.getTagCompound().copy()); } } } } world.func_147453_f(x, y, z, block); } super.breakBlock(world, x, y, z, block, metadata); } public boolean onBlockActivated(World world, int x, int y, int z, EntityPlayer player, int side, float hitX, float hitY, float hitZ) { if(world.isRemote) { return true; } else { player.openGui(ModPlantsandFoodPack.instance, 0, world, x, y, z); return true; } } public void onBlockPlacedBy(World world, int x, int y, int z, EntityLivingBase living, ItemStack stack){ if(stack.getItemDamage() == 0) { TileEntity tile = world.getTileEntity(x, y, z); if(tile instanceof TileEntityKitchenCupboard) { int direction = MathHelper.floor_double((double)(living.rotationYaw * 4.0F / 360.0F) + 2.5D) & 3; ((TileEntityKitchenCupboard)tile).setDirection((byte)direction); if(stack.hasDisplayName()) { ((TileEntityKitchenCupboard)tile).setCustomName(stack.getDisplayName()); } } } } @Override public boolean rotateBlock(World world, int x, int y, int z, ForgeDirection axis) { if((axis == ForgeDirection.UP || axis == ForgeDirection.DOWN) && !world.isRemote && world.getBlockMetadata(x, y, z) == 0) { TileEntity tile = world.getTileEntity(x, y, z); if(tile instanceof TileEntityKitchenCupboard) { TileEntityKitchenCupboard tileKitchenCupboard = (TileEntityKitchenCupboard)tile; byte direction = tileKitchenCupboard.getDirection(); direction++; if(direction > 3) { direction = 0; } tileKitchenCupboard.setDirection(direction); return true; } } return false; } public ForgeDirection[] getValidRotation(World world, int x, int y, int z) { return world.getBlockMetadata(x, y, z) == 0 ? new ForgeDirection[] {ForgeDirection.UP, ForgeDirection.DOWN,} : ForgeDirection.VALID_DIRECTIONS; } public boolean isOpaqueCube(){ return false; } public boolean renderAsNormalBlock(){ return false; } public int getRenderType(){ return ClientProxy.tesrRenderId; } }
Classe du Tile Entity :
package mod.plantsandfoodpack.common; 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.minecraft.network.NetworkManager; import net.minecraft.network.Packet; import net.minecraft.network.play.server.S35PacketUpdateTileEntity; import net.minecraft.tileentity.TileEntity; import net.minecraftforge.common.util.Constants; public class TileEntityKitchenCupboard extends TileEntity implements IInventory { private byte direction; private ItemStack[] contents = new ItemStack[18]; private String customName; @Override public void readFromNBT(NBTTagCompound compound) { super.readFromNBT(compound); this.direction = compound.getByte("Direction"); if (compound.hasKey("CustomName", Constants.NBT.TAG_STRING)) { this.customName = compound.getString("CustomName"); } NBTTagList nbttaglist = compound.getTagList("Items", Constants.NBT.TAG_COMPOUND); this.contents = new ItemStack[this.getSizeInventory()]; for (int i = 0; i < nbttaglist.tagCount(); ++i) { NBTTagCompound nbttagcompound1 = nbttaglist.getCompoundTagAt(i); int j = nbttagcompound1.getByte("Slot") & 255; if (j >= 0 && j < this.contents.length) { this.contents[j] = ItemStack.loadItemStackFromNBT(nbttagcompound1); } } } public void writeToNBT(NBTTagCompound compound) { super.writeToNBT(compound); compound.setByte("Direction", this.direction); if (this.hasCustomInventoryName()) { compound.setString("CustomName", this.customName); } NBTTagList nbttaglist = new NBTTagList(); for (int i = 0; i < this.contents.length; ++i) { if (this.contents* != null) { NBTTagCompound nbttagcompound1 = new NBTTagCompound(); nbttagcompound1.setByte("Slot", (byte)i); this.contents*.writeToNBT(nbttagcompound1); nbttaglist.appendTag(nbttagcompound1); } } compound.setTag("Items", nbttaglist); } public byte getDirection() { return direction; } public void setDirection(byte direction) { this.direction = direction; this.worldObj.markBlockForUpdate(this.xCoord, this.yCoord, this.zCoord); } public Packet getDescriptionPacket() { NBTTagCompound nbttagcompound = new NBTTagCompound(); this.writeToNBT(nbttagcompound); return new S35PacketUpdateTileEntity(this.xCoord, this.yCoord, this.zCoord, 0, nbttagcompound); } public void onDataPacket(NetworkManager net, S35PacketUpdateTileEntity pkt) { this.readFromNBT(pkt.func_148857_g()); this.worldObj.markBlockRangeForRenderUpdate(this.xCoord, this.yCoord, this.zCoord, this.xCoord, this.yCoord, this.zCoord); } @Override public int getSizeInventory() { return this.contents.length; } @Override public ItemStack getStackInSlot(int slotIndex) { return this.contents[slotIndex]; } @Override public ItemStack decrStackSize(int slotIndex, int amount) { if (this.contents[slotIndex] != null) { ItemStack itemstack; if (this.contents[slotIndex].stackSize <= amount) { itemstack = this.contents[slotIndex]; this.contents[slotIndex] = null; this.markDirty(); return itemstack; } else { itemstack = this.contents[slotIndex].splitStack(amount); if (this.contents[slotIndex].stackSize == 0) { this.contents[slotIndex] = null; } this.markDirty(); return itemstack; } } else { return null; } } @Override public ItemStack getStackInSlotOnClosing(int slotIndex) { if (this.contents[slotIndex] != null) { ItemStack itemstack = this.contents[slotIndex]; this.contents[slotIndex] = null; return itemstack; } else { return null; } } @Override public void setInventorySlotContents(int slotIndex, ItemStack stack) { this.contents[slotIndex] = stack; if (stack != null && stack.stackSize > this.getInventoryStackLimit()) { stack.stackSize = this.getInventoryStackLimit(); } this.markDirty(); } @Override public String getInventoryName() { return this.hasCustomInventoryName() ? this.customName : "tile.kitchenCupboard"; } @Override public boolean hasCustomInventoryName() { return this.customName != null && !this.customName.isEmpty(); } public void setCustomName(String customName) { this.customName = customName; } @Override public int getInventoryStackLimit() { return 64; } @Override public boolean isUseableByPlayer(EntityPlayer player) { return this.worldObj.getTileEntity(this.xCoord, this.yCoord, this.zCoord) != this ? false : player.getDistanceSq((double)this.xCoord + 0.5D, (double)this.yCoord + 0.5D, (double)this.zCoord + 0.5D) <= 64.0D; } @Override public void openInventory() { } @Override public void closeInventory() { } @Override public boolean isItemValidForSlot(int slotIndex, ItemStack stack) { return true; } }
Classe du GuiHandler :
package mod.plantsandfoodpack.common; import mod.plantsandfoodpack.client.GuiKitchenCupboard; import net.minecraft.entity.player.EntityPlayer; import net.minecraft.tileentity.TileEntity; import net.minecraft.world.World; import cpw.mods.fml.common.network.IGuiHandler; public class GuiHandlerPlantsAndFoodPack implements IGuiHandler { @Override public Object getServerGuiElement(int ID, EntityPlayer player, World world, int x, int y, int z) { TileEntity tile = world.getTileEntity(x, y, z); if(tile instanceof TileEntityKitchenCupboard) { return new ContainerKitchenCupboard((TileEntityKitchenCupboard)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(x, y, z); if(tile instanceof TileEntityKitchenCupboard) { return new GuiKitchenCupboard((TileEntityKitchenCupboard)tile, player.inventory); } return null; } }
Classe du Container :
package mod.plantsandfoodpack.common; import net.minecraft.entity.player.EntityPlayer; import net.minecraft.entity.player.InventoryPlayer; import net.minecraft.init.Blocks; import net.minecraft.inventory.Container; import net.minecraft.inventory.Slot; import net.minecraft.item.Item; import net.minecraft.item.ItemStack; public class ContainerKitchenCupboard extends Container { private final TileEntityKitchenCupboard tileKitchenCupboard; public ContainerKitchenCupboard(TileEntityKitchenCupboard tile, InventoryPlayer inventory) { this.tileKitchenCupboard = tile; tile.openInventory(); for (int i = 0; i < 3; ++i) { for (int j = 0; j < 9; ++j) { this.addSlotToContainer(new Slot(tile, j + i * 9, 8 + j * 18, 18 + i * 18)); } } this.bindPlayerInventory(inventory); } private void bindPlayerInventory(InventoryPlayer inventory) { int i; for (i = 0; i < 3; ++i) { for (int j = 0; j < 9; ++j) { this.addSlotToContainer(new Slot(inventory, j + i * 9 + 9, 8 + j * 18, 85 + i * 18 -18)); } } for (i = 0; i < 9; ++i) { this.addSlotToContainer(new Slot(inventory, i, 8 + i * 18, 143)); } } public ItemStack transferStackInSlot(EntityPlayer p_82846_1_, int slotIndex) { ItemStack itemstack = null; Slot slot = (Slot)this.inventorySlots.get(slotIndex); if (slot != null && slot.getHasStack()) { ItemStack itemstack1 = slot.getStack(); itemstack = itemstack1.copy(); if (slotIndex < this.tileKitchenCupboard.getSizeInventory()) { if (!this.mergeItemStack(itemstack1, this.tileKitchenCupboard.getSizeInventory(), this.inventorySlots.size(), true)) { return null; } } else if (!this.mergeItemStack(itemstack1, 0, this.tileKitchenCupboard.getSizeInventory(), false)) { return null; } if (itemstack1.stackSize == 0) { slot.putStack((ItemStack)null); } else { slot.onSlotChanged(); } } return itemstack; } @Override public boolean canInteractWith(EntityPlayer player) { return this.tileKitchenCupboard.isUseableByPlayer(player); } public void onContainerClosed(EntityPlayer player) { super.onContainerClosed(player); this.tileKitchenCupboard.closeInventory(); } }
Classe du Gui :
package mod.plantsandfoodpack.client; import java.awt.Container; import mod.plantsandfoodpack.common.ContainerKitchenCupboard; import mod.plantsandfoodpack.common.TileEntityKitchenCupboard; import net.minecraft.client.gui.inventory.GuiContainer; import net.minecraft.entity.player.InventoryPlayer; public class GuiKitchenCupboard extends GuiContainer { public GuiKitchenCupboard(TileEntityKitchenCupboard tile, InventoryPlayer inventory) { super(new ContainerKitchenCupboard(tile, inventory)); } @Override protected void drawGuiContainerBackgroundLayer(float p_146976_1_, int p_146976_2_, int p_146976_3_) { } }
Au passage, j’aimera savoir comment je pourrais faire pour que sur mon Gui, il y est 3 rangées de 6 slots ?
Merci d’avance !