Salut à tous ! Vous avez été nombreux à me demander de faire ce tutoriel car il vous semble intéressant de faire un item qui fait un zoom/dé-zoom lors d’un clique droit. Et bien chose promise chose due ! 
- Les pré-requis
- La classe principale
- Le Tick Handler
- La classe de l’Item
- Le rendu final des classes
- BONUS
-ATTENTION-
Ce tutoriel ne modifie en aucun cas le FOV de Minecraft !! Nous allons faire un vrai zoom !
Pour commencer, vous devez avoir des connaissances en java et savoir créer un item ainsi que maîtriser le TickHandler.
Dans la classe principale de notre Mod, nous allons y ajouter un boolean que nous allons appeler “zoom” juste au dessus de votre méthode de pré-initialisation :
public static boolean zoom;
Oui, le boolean doit être STATIC ! Sinon vous aurez une erreur ! (Pas toute de suite, mais lorsque nous en aurons besoin.)
Oui mais pourquoi ne doit-on pas mettre = false ?
-Car un boolean est automatiquement en false quand il n’y a pas d’égale.
Eh oui, il fallait s’en douter, mais pour que le zoom soit fonctionnel, nous aurons besoin d’un TickHandler ! (Par pitié, ne ramenez pas la fonction “onUpdate” de la classe Item ! Le zoom ne fonctionne pas avec !)
Pour les zoom, nous n’utiliserons pas un TickCommonHandler, mais plutôt un TickClientHandler ;). Le serveur n’a pas à gérer le zoom, mais le boolean ! 
Donc faites une classe avec un nom quelconque du moment qu’elle implémente ITickHandler.
Donc moi je vais l’appeler “TickClientHandler” comme ceci :
public class TickClientHandler implements ITickHandler
Ensuite vous allez devoir ajouter ces méthodes dans la classe :
| @Override |
| public void tickStart(EnumSet<TickType> type, Object… tickData) |
| { |
| } |
| |
| @Override |
| public void tickEnd(EnumSet<TickType> type, Object... tickData) |
| { |
| } |
| |
| @Override |
| public EnumSet<TickType> ticks() |
| { |
| return EnumSet.of(TickType.RENDER, TickType.CLIENT); |
| } |
| |
| @Override |
| public String getLabel() |
| { |
| return "TickClientHandler"; |
| } |
Ajoutez-les bien comme je vous l’ai montré ! Car j’ai un peu modifié certaines méthodes comme celle-ci :
| @Override |
| public EnumSet<TickType> ticks() |
| { |
| return EnumSet.of(TickType.RENDER, TickType.CLIENT); |
| } |
On a déjà fini ?!
-Euh… Non… ^^ Loin de là même. 
Maintenant, nous allons passer aux choses sérieuses ! 
Tout en bas de votre classe, créez un void statique que nous allons appeler “zoom” et y ajouter un float comme paramètre.
Ex :
| public static void zoom(float value) |
| { |
| } |
C’est ici que nous allons ajouter une ligne qui va faire appel à vos connaissances Java… J’ai nommé : La reflection ! 
La plupart du temps, on fait ses propres lignes de reflection java (oui reflection et pas réflexion, la JVM n’a pas de cerveau et ne réfléchit pas ^^), mais grâce à Forge, nous avons une version simplifié de ceci !
Et ce, en une ligne !
C’est bien beau tout cela, mais… C’est quoi cette fameuse “ligne” ?
Tout simplement celle qui va permettre la modification du zoom. 
Pourquoi utiliser cette ligne alors que nous pouvions tout simplement appeler cameraZoom de EntityRenderer ?
Tout simplement que le field cameraZoom n’est pas accessible. Et que pour rendre accessible un field sans modifier la classe qui le contient, c’est là que la reflection java intervient.
Dans votre methode zoom, ajoutez cette ligne :
ObfuscationReflectionHelper.setPrivateValue(EntityRenderer.class, FMLClientHandler.instance().getClient().entityRenderer, value, "cameraZoom");
Pour résumer cette ligne : Elle va définir la valeur “value” (nom de notre float dans le paramètre de la méthode) au field cameraZoom (il est appelé en String car cela ne servirait à rien sinon)
Maintenant que nous avons fait notre fonction zoom, il faut l’utiliser maintenant ! 
Rendez-vous dans la méthode tickEnd et ajoutez ces deux déclarations :
| final Minecraft minecraft = FMLClientHandler.instance().getClient(); |
| final EntityPlayerSP player = minecraft.thePlayer; |
Et pour ceux comme moi qui connaissent les TickHandler, il faut jamais oublier cette condition :
| if (type.equals(EnumSet.of(TickType.CLIENT))) |
| { |
| } |
Et c’est dans cette condition que nous allons rendre notre code utile ! 
Certains d’entre vous l’aurons compris, nous allons faire une autre condition DANS celle-ci !
Ajoutez cette autre condition dans la condition que je vous ai montré ci dessus :
| if(VotreClassPrincipale.zoom) |
| { |
| } else { |
| } |
Oh tiens, un else ! Mais oui voyons, c’est normal ! 
Remplacez VotreClassPrincipale par le nom de votre class principale.
Nous allons, une troisième et dernière fois, ajouter une condition qui va vérifier si le joueur est bien à la première personne et n’a aucun GUI d’ouvert !
| if(minecraft.gameSettings.thirdPersonView == 0 && minecraft.currentScreen == null) |
| { |
| } |
Maintenant, on ajoute (enfin) notre zoom(value); ! 
zoom(value);
Remplacez value par la quantité de zoom, pour ce tutoriel, nous allons mettre 6.0F(c’est déjà pas mal).
Et dans le else de la condition if(VotreClassePrincipale.zoom), ajoutez de nouveau cette même ligne :
zoom(1.0F);
Pourquoi 1.0F ?
-1.0 est le zoom par défaut de la caméra. (Donc pour récupérer la vue normal.)
Votre méthode tickEnd doit ressembler à ça (normalement) :
| public void tickEnd(EnumSet<TickType> type, Object... tickData) |
| { |
| final Minecraft minecraft = FMLClientHandler.instance().getClient(); |
| final EntityPlayerSP player = minecraft.thePlayer; |
| if (type.equals(EnumSet.of(TickType.CLIENT))) |
| { |
| if(VotreClassePrincipale.zoom) |
| { |
| if(minecraft.gameSettings.thirdPersonView == 0 && minecraft.currentScreen == null) |
| { |
| zoom(6.0F); |
| } |
| }else { |
| zoom(1.0F); |
| } |
| } |
| } |
Et voilà, nous avons fini la partie qui consistait à créer le zoom ! 
Maintenant il nous faut appeler le boolean dans notre classe de l’item !
Dans cette partie, je ne vais pas vous apprendre à créer un item basique, je vous invite à voir le tutoriel de robin pour cela !
Donc je vais nommer notre classe ItemZoomer !
Faites ensuite la base de l’item (c’est à dire la classe avec le constructeur ainsi que sa déclaration dans la classe principale, etc…)
Pour utiliser notre zoom avec l’item, nous aurons besoin d’utiliser la fonction onRightClick de Item.java :
| public ItemStack onItemRightClick(ItemStack stack, World world, EntityPlayer player) |
| { |
| return stack; |
| } |
Et c’est là que se joue la touche finale ! 
Juste au dessus de “return stack” ajoutez la condition et ces quelques lignes suivante :
| if(!world.isRemote) |
| { |
| VotreClassePrincipale.zoom = !VotreClassePrincipale.zoom; |
| } |
Encore une fois remplacez VotreClassePrincipale par le nom de votre classe principale 
Et voilà, nous avons créer notre bel item zoomer ! 
La classe principale (exemple) :
public static Item itemZoomer;
et dans votre methode d’initialisation :
| itemZoomer = new ItemZoomer(votreID).setCreativeTab(Votre_Creative_Tab).setUnlocalizedName("itemZoomer").setTextureName("monModID:itemZoomer"); |
| TickRegistry.registerTickHandler(new TickClientHandler(), Side.CLIENT); |
Au dessus de votre méthode de pré-initialisation :
public static boolean zoom;
Le TickClientHandler :
| public class TickClientHandler implements ITickHandler { |
| |
| @Override |
| public void tickStart(EnumSet<TickType> type, Object... tickData) { |
| final Minecraft minecraft = FMLClientHandler.instance().getClient(); |
| } |
| |
| @Override |
| public void tickEnd(EnumSet<TickType> type, Object... tickData) |
| { |
| final Minecraft minecraft = FMLClientHandler.instance().getClient(); |
| final EntityPlayerSP player = minecraft.thePlayer; |
| if (type.equals(EnumSet.of(TickType.CLIENT))) |
| { |
| if(VotreClassePrincipale.zoom) |
| { |
| if(minecraft.gameSettings.thirdPersonView == 0 && minecraft.currentScreen == null) |
| { |
| zoom(6F); |
| } |
| }else { |
| zoom(1.0F); |
| } |
| |
| } |
| } |
| |
| @Override |
| public EnumSet<TickType> ticks() { |
| return EnumSet.of(TickType.RENDER, TickType.CLIENT); |
| } |
| |
| @Override |
| public String getLabel() { |
| return "TickClientHandler"; |
| } |
| |
| public static void zoom(float value) |
| { |
| ObfuscationReflectionHelper.setPrivateValue(EntityRenderer.class, FMLClientHandler.instance().getClient().entityRenderer, value, "cameraZoom"); |
| } |
| } |
La classe de l’item :
| public class ItemZoomer extends Item{ |
| |
| public ItemZoomer(int i) |
| { |
| super(i); |
| } |
| |
| @Override |
| public ItemStack onItemRightClick(ItemStack stack, World world, EntityPlayer player) |
| { |
| if(!world.isRemote) |
| { |
| VotreClassePrincipale.zoom = !VotreClassePrincipale.zoom; |
| } |
| return stack; |
| } |
| } |
Dans ce tutoriel je n’ai pas mis les packages ou autres trucs que vous êtes censé vous même inclure. (Ce qui explique pourquoi je n’ai pas mis de class principal intégrale dans la partie “rendue final” 
Pour le bonus, pas grand chose… Ah si ! Ajouter un overlay pour donner un petit effet à votre zoom ! 
Overlay ? C’est quoi ça ? 
Ça : http://puu.sh/46id6.png
Pour se faire, c’est simple, ajoutez cette ligne tout en haut de votre TickHandler :
public static final ResourceLocation zoomOverlay = new ResourceLocation("votreModID", "textures/blur/votreZoomOverlay.png");
Comme vous l’aurez compris il s’agit d’une texture que nous allons imposer à l’écran ! 
-ATTENTION-
La texture de l’overlay doit être en 256x256 ! (si vous faites un téléscope (par exemple), autant vous donner l’overlay : http://puu.sh/46iD2.png Maintenant vous avez un exemple de ce que je veux dire. En effet, la texture semble écrasé, mais in game elle ne le sera pas, car grâce à la méthode que je vais vous montrer, nous allons “étirer” la texture histoire qu’elle ressemble à quelque chose !
)
Donc dans votre classe, ajoutez cette méthode :
| public static void renderTextureOverlay(ResourceLocation s, float f) |
| { |
| Minecraft minecraft = FMLClientHandler.instance().getClient(); |
| ScaledResolution scaledresolution = new ScaledResolution(minecraft.gameSettings, minecraft.displayWidth, minecraft.displayHeight); |
| int i = scaledresolution.getScaledWidth(); |
| int j = scaledresolution.getScaledHeight(); |
| GL11.glEnable(GL11.GL_BLEND); |
| GL11.glDisable(GL11.GL_DEPTH_TEST); |
| GL11.glDepthMask(false); |
| GL11.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA); |
| GL11.glColor4f(1.0F, 1.0F, 1.0F, f); |
| GL11.glDisable(GL11.GL_ALPHA_TEST); |
| minecraft.getTextureManager().bindTexture(s); |
| Tessellator tessellator = Tessellator.instance; |
| tessellator.startDrawingQuads(); |
| tessellator.addVertexWithUV(0.0D, j, -90D, 0.0D, 1.0D); |
| tessellator.addVertexWithUV(i, j, -90D, 1.0D, 1.0D); |
| tessellator.addVertexWithUV(i, 0.0D, -90D, 1.0D, 0.0D); |
| tessellator.addVertexWithUV(0.0D, 0.0D, -90D, 0.0D, 0.0D); |
| tessellator.draw(); |
| GL11.glDepthMask(true); |
| GL11.glEnable(GL11.GL_DEPTH_TEST); |
| GL11.glEnable(GL11.GL_ALPHA_TEST); |
| GL11.glColor4f(1.0F, 1.0F, 1.0F, f); |
| } |
On a presque fini ! Nous avons préparé ce dont nous avons besoin. 
Créez une nouvelle condition en dessous de :
| if (type.equals(EnumSet.of(TickType.CLIENT))) |
| { |
| } |
Elle est exactement la même, juste à remplacer CLIENT par RENDER, donc comme ceci :
| if (type.equals(EnumSet.of(TickType.RENDER))) |
| { |
| } |
Et dans cette condition nous allons ajouter encore une AUTRE condition qui va appeler la méthode que nous avons faite ci dessus ! 
| if(VotreClassePrincipale.zoom && minecraft.gameSettings.thirdPersonView == 0 && minecraft.currentScreen == null) |
| { |
| renderTextureOverlay(zoomOverlay, 1.0F); |
| } |
C’est tout ?!
Oui, déjà fini ^^
Voilà ce à quoi votre classe TickHandler devrait ressembler :
| public class TickClientHandler implements ITickHandler |
| { |
| public static final ResourceLocation zoomOverlay = new ResourceLocation("votreModID", "textures/blur/votreZoomOverlay.png"); |
| |
| @Override |
| public void tickStart(EnumSet<TickType> type, Object... tickData) { |
| } |
| |
| @Override |
| public void tickEnd(EnumSet<TickType> type, Object... tickData) |
| { |
| final Minecraft minecraft = FMLClientHandler.instance().getClient(); |
| final EntityPlayerSP player = minecraft.thePlayer; |
| if (type.equals(EnumSet.of(TickType.CLIENT))) |
| { |
| if(VotreClassePrincipale.zoom) |
| { |
| if(minecraft.gameSettings.thirdPersonView == 0 && minecraft.currentScreen == null) |
| { |
| zoom(6F); |
| } |
| }else { |
| zoom(1.0F); |
| } |
| } |
| if (type.equals(EnumSet.of(TickType.RENDER))) |
| { |
| if(VotreClassePrincipale.zoom && minecraft.gameSettings.thirdPersonView == 0 && minecraft.currentScreen == null) |
| { |
| renderTextureOverlay(zoomOverlay, 1.0F); |
| } |
| } |
| } |
| |
| public static void renderTextureOverlay(ResourceLocation s, float f) |
| { |
| Minecraft minecraft = FMLClientHandler.instance().getClient(); |
| ScaledResolution scaledresolution = new ScaledResolution(minecraft.gameSettings, minecraft.displayWidth, minecraft.displayHeight); |
| int i = scaledresolution.getScaledWidth(); |
| int j = scaledresolution.getScaledHeight(); |
| GL11.glEnable(GL11.GL_BLEND); |
| GL11.glDisable(GL11.GL_DEPTH_TEST); |
| GL11.glDepthMask(false); |
| GL11.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA); |
| GL11.glColor4f(1.0F, 1.0F, 1.0F, f); |
| GL11.glDisable(GL11.GL_ALPHA_TEST); |
| minecraft.getTextureManager().bindTexture(s); |
| Tessellator tessellator = Tessellator.instance; |
| tessellator.startDrawingQuads(); |
| tessellator.addVertexWithUV(0.0D, j, -90D, 0.0D, 1.0D); |
| tessellator.addVertexWithUV(i, j, -90D, 1.0D, 1.0D); |
| tessellator.addVertexWithUV(i, 0.0D, -90D, 1.0D, 0.0D); |
| tessellator.addVertexWithUV(0.0D, 0.0D, -90D, 0.0D, 0.0D); |
| tessellator.draw(); |
| GL11.glDepthMask(true); |
| GL11.glEnable(GL11.GL_DEPTH_TEST); |
| GL11.glEnable(GL11.GL_ALPHA_TEST); |
| GL11.glColor4f(1.0F, 1.0F, 1.0F, f); |
| } |
| |
| @Override |
| public EnumSet<TickType> ticks() { |
| |
| return EnumSet.of(TickType.RENDER, TickType.CLIENT); |
| } |
| |
| public static void zoom(float value) |
| { |
| ObfuscationReflectionHelper.setPrivateValue(EntityRenderer.class, FMLClientHandler.instance().getClient().entityRenderer, value, "cameraZoom"); |
| } |
| |
| @Override |
| public String getLabel() { |
| return "TickClientHandler"; |
| } |
| } |
J’espère que ce tutoriel vous aura plu, il m’a beaucoup été demandé, donc je l’ai fait. Et encore il faut que je me relise, car je suis persuadé que j’ai fait des fautes (j’ai passé la journée à faire ce tutoriel ^^).