Bloc de type four
-
Sommaire
Introduction
Nous allons créer un bloc qui prend des items en entrée et qui sort d’autres items en sortie comme à la façon d’un four par exemple.
Pour cet exemple j’ai choisi un bloc qui prendra 4 items en entrée, 1 item pour alimenter et qui sortira 2 items.
Je vous montrerez cependant comment personnaliser le code pour obtenir ce que vous voulez mais pour cela il va falloir
bien comprendre le code donc soyer attentif.Pré-requis
Code
La classe principale
On va commencer par déclarer notre bloc, dans ce tutoriel je l’appellerai machineTuto
public static Block machineTuto;
Pensez à respecter la convention Java, cette variable commence par une minuscule.
Dans la méthode preInit nous allons instancier notre bloc
machineTuto = new BlockMachineTuto();
Encore une fois la convention Java préconise que les classes commencent par une majuscule.
Créez la classe BlockMachineTuto.Toujours dans la classe principale mais cette fois dans la méthode init nous allons enregistrer notre TileEntity
GameRegistry.registerTileEntity(TileEntityMachineTuto.class, "tileentitymachinetuto");
Créez la classe TileEntityMachineTuto.
Il faut aussi enregistrer le Gui Handler :
NetworkRegistry.INSTANCE.registerGuiHandler(instance, new GuiHandlerTuto());
Voilà pour la classe principale.
La classe du bloc
Allez maintenant dans la classe du bloc et faite-la étendre à BlockContainer
public class BlockMachineTuto extends BlockContainer
Ajoutez le constructeur
public BlockMachineTuto() { super(Material.rock); //Choisissez ce que que vous voulez //Autres paramètres }
On va maintenant déclarer que le bloc possède un TileEntity et dire quel TileEntity créer
@Override public boolean hasTileEntity() { return true; } @Override public TileEntity createNewTileEntity(World world, int metadata) { return new TileEntityMachineTuto(); }
Maintenant occupons-nous de la méthode qui va permettre de drop les items quand on casse le bloc
@Override public void breakBlock(World world, BlockPos pos, IBlockState state) { TileEntity tile = world.getTileEntity(pos); if(tile instanceof TileEntityMachineTuto) { InventoryHelper.dropInventoryItems(world, pos, (TileEntityMachineTuto)tile); } super.breakBlock(world, pos, state); }
Et maintenant la méthode pour ouvrir le gui lorsqu’on fait clique droit sur le bloc
@Override public boolean onBlockActivated(World world, BlockPos pos, IBlockState state, EntityPlayer player, EnumFacing side, float hitX, float hitY, float hitZ) { if(world.isRemote) { return true; } else { player.openGui(ModTutoriel.instance, 0, world, pos.getX(), pos.getY(), pos.getZ()); return true; } }
Pour que votre bloc soit rendu comme un bloc normal il va falloir Override la fonction getRenderType
@Override public int getRenderType() { return 3; }
Voilà pour la classe du bloc
La classe du TileEntity
Allez dans la classe du TileEntity (TileEntityMachineTuto), c’est ici qu’une grande partie
du système va se dérouler il est donc important de comprendre.On va déclarer plusieurs variables
private String customName; public static final int INPUTS_SLOTS = 4; public static final int FUEL_SLOTS = 1; public static final int OUTPUT_SLOTS = 2; private ItemStack[] contents = new ItemStack[INPUTS_SLOTS + FUEL_SLOTS + OUTPUT_SLOTS]; private int currentWorkingTime = 0; private int totalWorkingTime = 300; private int remainingFuelTime = 0;
Quelques explications :
-customName contient le nom custom du bloc si il en a un
-INPUTS_SLOTS contient le nombre de slots de type input (slots inputs = slots où on va mettre les items pour la recette)
-FUEL_SLOTS contient le nombre de slots où on va mettre le carburant;
-OUTPUT_SLOTS contient le nombre de slots d’output (slots output = slots où les items créés vont)
-contents contient les ItemStack de votre bloc autrement dit tout les slots, c’est ici que sont stockés les items
-currentWorkingTime contient l’avancement de la recette, il représente le temps passé
-totalWorkingTime contient le temps nécessaire pour que la recette soit finie
-remainingFuelTime contient la quantitée de carburant restantMaintenant é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’outputprivate 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’outputprivate 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 itemprivate 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_planksVoilà 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 tileOn 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 à GuiContainerpublic 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 containerA 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 afficherQuand vous utilisez cette fonction, il faut associer la texture au textureManager de minecraft, il faut
donc mettre 1 fois au début de la fonctionGL11.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 textePour 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 writeIl 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 rajouterif(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 TileEntityEt 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
-
Plus qu’à regarder si on garde ou pas, si on le garde je commit sur GitHub le code
-
Beau tuto ! Bien joué
-
Merci beaucoup pour ce tuto.
-
STP REMET LE GITHUB