• S'inscrire
    • Se connecter
    • Recherche
    • Récent
    • Mots-clés
    • Populaire
    • Utilisateurs
    • Groupes

    Bloc de type four

    Tutoriels des membres
    1.8
    4
    5
    2468
    Charger plus de messages
    • Du plus ancien au plus récent
    • Du plus récent au plus ancien
    • Les plus votés
    Répondre
    • Répondre à l'aide d'un nouveau sujet
    Se connecter pour répondre
    Ce sujet a été supprimé. Seuls les utilisateurs avec les droits d'administration peuvent le voir.
    • BrokenSwing
      BrokenSwing Moddeurs confirmés Rédacteurs dernière édition par robin4002

      Sommaire

      • Introduction
      • Pré-requis
      • Code
        • La classe principale
        • La classe du bloc
        • La classe du TileEntity
        • La classe des recettes
        • La classe du container
        • La classe du SlotResult
        • La classe du GUI
      • Bonus
      • Résultat

      Introduction

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

      Pré-requis

      • Créer un bloc basique
      • Créer un gui et un container sur un bloc
      • Ajouter un TileEntity au bloc

      Code

      La classe principale

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

      public static Block machineTuto;
      

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

      Dans la méthode preInit nous allons instancier notre bloc

      machineTuto = new BlockMachineTuto();
      

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

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

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

      Créez la classe TileEntityMachineTuto.

      Il faut aussi enregistrer le Gui Handler :

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

      Voilà pour la classe principale.

      La classe du bloc

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

      public class BlockMachineTuto extends BlockContainer
      

      Ajoutez le constructeur

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

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

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

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

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

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

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

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

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

      Voilà pour la classe du bloc

      La classe du TileEntity

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

      On va déclarer plusieurs variables

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

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

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

      public class TileEntityMachineTuto extends TileEntity implements IUpdatePlayerListBox, ISidedInventory
      

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

      D’autres informations peuvent être utiles comme :

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

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

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

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

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

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

      Maintenant une fonction pour savoir si on peut lancer la recette

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

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

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

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

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

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

      Pour sauvegarder le tout il nous faut envoyer les petits packets

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

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

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

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

      Voilà pour la classe du TileEntity

      La classe des recettes

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

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

      private static final RecipesMachineTuto = new RecipesMachineTuto();
      

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

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

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

      
      public static RecipesMachineTuto instance() {
          return INSTANCE;
      }
      
      

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

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

      Maintenant on fait la même chose pour les recettes

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

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

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

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

      Voilà pour la classe des recettes

      La classe du container

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

      public class ContainerMachineTuto extends Container
      

      Déclarez lui 4 variables

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

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

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

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

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

      Pour transférer un stack dans un slot

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

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

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

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

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

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

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

      Voilà pour le container

      La classe du SlotResult

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

      public class SlotOutput extends Slot
      

      Mettez comme constructeur ceci :

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

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

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

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

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

      Voilà pour la classe SlotOutput

      La classe du GUI

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

      public class GuiMachineTuto extends GuiContainer
      

      Il faut ensuite déclarer 3 variables

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

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

      A présent mettez le constructeur suivant

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

      Les deux fonctions suivantes permettent de dessiner le Gui :

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

      Vous pouvez dessiner à l’aide des fonctions :

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

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

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

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

      On peut écrire à l’aide de cette fonction

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

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

      Pour notre machineTuto j’ai codé ceci :

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

      Avec cette texture

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

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

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

      Bonus

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

      Dans la classe du TileEntity rajoutez 3 variables :

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

      Explications :

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

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

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

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

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

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

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

      Juste au dessus de

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

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

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

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

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

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

      Voilà pour la modification de la classe du TileEntity

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

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

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

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

      Il faut utiliser cette texture là :

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

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

      Résultat

      Commit sur GitHub

      1 réponse Dernière réponse Répondre Citer 0
      • BrokenSwing
        BrokenSwing Moddeurs confirmés Rédacteurs dernière édition par

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

        1 réponse Dernière réponse Répondre Citer 0
        • Deleted
          Deleted dernière édition par

          Beau tuto ! Bien joué 😃

          1 réponse Dernière réponse Répondre Citer 0
          • N
            Nyhillius dernière édition par

            Merci beaucoup pour ce tuto. 🙂

            1 réponse Dernière réponse Répondre Citer 0
            • Y
              Youtube_Nosios Banni dernière édition par

              STP REMET LE GITHUB

              1 réponse Dernière réponse Répondre Citer 0
              • 1 / 1
              • Premier message
                Dernier message
              Design by Woryk
              Contact / Mentions Légales

              MINECRAFT FORGE FRANCE © 2018

              Powered by NodeBB