Un fluide


  • Administrateurs

    Dans ce tutoriel nous allons apprendre à créer un fluide, accrochez-vous bien, c'est plutôt compliqué !

    Pre-requis

    Dans la classe principale

    Pour commencer, déclarez votre fluide :

    public static Fluid fluidTutorial;
    

    Ensuite initialisé et enregistrer votre fluide avant les blocs :

             // Fluid
            fluidTutorial = new Fluid("tutorial").setDensity(4000).setViscosity(500).setTemperature(286).setLuminosity(10).setUnlocalizedName("tutorial");
            FluidRegistry.registerFluid(fluidTutorial);
            fluidTutorial = FluidRegistry.getFluid("tutorial");
    
            // Blocks
            BlockTutorial = new BlockTutorial(….....
    

    setDensity défini la densité du fluide, en quelque sorte sa masse. Si la valeur est négatif, votre fluide sera plus léger que l'air et s'envolera.
    setViscosity défini la viscosité, elle est lié à la vitesse de tick de votre bloc. Plus cette valeur est élevé, plus le fluide se déplacera lentement.
    setTemperature défini la température en degré Kelvin, donc 273 de plus que la température en degré Celsius. 0 correspond donc au zéro absolu, et 273 à 0° C. Par défaut cette valeur vaut 295, soit 22° C.
    setLuminosity rend votre fluide lumineux. Par défaut sur 0 et ne fait pas de lumière, la lave est sur 15.
    setUnlocalizedName défini le nom non localisé du fluid.

    Important, dans le constructeur, donc Fluid("tutorial") le nom ne doit pas avoir de majuscule. Si vous mettez des majuscules, elles seront remit en minuscule, évitez donc les majuscules pour éviter tous problèmes

    Ensuite on enregistre le fluid puis on ré-initialise le fluid avec ce code :

        fluidTutorial = FluidRegistry.getFluid("tutorial");
    

    Pourquoi ?
    Car il ne peut pas avoir deux blocs qui correspond à un même fluide, si cela arrive, le jeu ba crasher. Pour éviter tout éventuel conflit avec un autre mod qui ajoute un fluide du même nom, il faut ré-initialisé la valeur du fluide à la valeur du FluidRegistry de Forge. Ainsi si un autre mod ajoute aussi un fluide tutorial, les deux fluides seront les mêmes comme ils correspondront tout les deux à FluidRegistry.getFluid("tutorial").

    Ensuite on passe au bloc. On le déclare :

        public static Block blockFluidTutorial
    

    Et on l'initialise à la suite des blocs dans la version preInit :

            if(fluidTutorial.getBlockID() == -1)
            {
                blockFluidTutorial = new BlockFluidTutorial(fluidTutorialID, fluidTutorial, Material.water).setUnlocalizedName("fluidTutorial");
                GameRegistry.registerBlock(blockFluidTutorial, "fluidTutorial");
                fluidTutorial.setBlockID(blockFluidTutorial);
            }
            else
            {
                blockFluidTutorial = Block.blocksList[fluidTutorial.getBlockID()];
            }
    

    Une fois de plus, une petite prévention pour éviter les éventuels risque de conflit avec un autre mod.
    Dans le cas au aucun mod n'ajoute ce fluide, alors l'id du fluide sera toujours -1 (car l'id d'un fluide est par défaut -1)
    Donc on initialise notre bloc, on l'enregistre, et initialise l'id du fluide à la même que le bloc.
    Si un autre mod ajoute déjà ce même fluide, le fluide sera déjà initialisé, et son id ne sera donc pas -1. Dans ce cas notre bloc devient le bloc déjà existant ajouté par l'autre mod.

    Le constructeur est un peu différent des blocs basiques :
    fluidTutorialID, fluidTutorial, Material.water
    fluidTutorialID est l'id de mon bloc, fluidTutorial correspond à mon fluide (d'où l’intérêt de l'initialisé avant le bloc, car sinon il sera null et ça va cause un NPE (NullPointerException) et Material.water est le matériel de mon bloc.

    Ensuite, déclarez un item, et initialisez-le :

            public static Item bucketTutorial;
    

    Dans preInit, à la suite des autres items :

            bucketTutorial = new ItemBucketTutorial(bucketTutorialID, blockFluidTutorial.blockID).setUnlocalizedName("bucketTutorial").setTextureName("modtutoriel:bucketTutorial").setContainerItem(Item.bucketEmpty);
    

    à nouveau, le constructeur est un peu différent, il faut ajouter leBlocDeVotreFluide.blockID.
    .setContainerItem(Item.bucketEmpty) indique que notre seau "contient" un seau vide, en conséquence lorsqu'il sera consumé, un seau vide sera mit dans l'inventaire.

            GameRegistry.registerItem(bucketTutorial, "BucketTutorial", "ModTutoriel");
    

    Optionnel, l'enregistrement de l'item.

    À la fin la méthode preInit, ajoutez :

            FluidContainerRegistry.registerFluidContainer(FluidRegistry.getFluidStack("tutorial", FluidContainerRegistry.BUCKET_VOLUME), new ItemStack(bucketTutorial), FluidContainerRegistry.EMPTY_BUCKET);
    

    FluidRegistry.getFluidStack("tutorial", FluidContainerRegistry.BUCKET_VOLUME) sert à instancier un FluidStack avec la quantité et le nom que nous avons mit dans le constructeur du fluide. Comme je l'ai dit plus haut, pas de majuscule dans ce nom.
    BUCKET_VOLUME correspond à 1000, vous pouvez très bien créer des minis seaux par exemple et mettre seulement 500.
    Ensuite il faut mettre un ItemStack qui correspond à votre seau plein, et un ItemStack qui correspond au seau vide. Vous n'êtes pas obliger de mettre l'ItemStack vide, cela mettra automatiquement le seau vide.

    Et en finir avec la classe principale, enregistrez deux event :

            MinecraftForge.EVENT_BUS.register(new TextureEvent());
            MinecraftForge.EVENT_BUS.register(new BucketEvent());
    

    Dans mon cas je veux enregistrer les deux event dans deux classes différentes, TextureEvent que je vais mettre dans mon package client et BucketEvent que je vais mettre dans le package common. C'est une façon de s'organiser, si vous préférer minimiser votre nombre de classe, vous pouvez mettre les deux event dans la même classe. Par exemple :

            MinecraftForge.EVENT_BUS.register(new FluidEvent())
    

    Avec les deux event dans la même classe. (ne vous inquiétez pas pour les sides, ils seront gérer avec un SideOnly).

    La classe du bloc

    Nous pouvons enfin passez au bloc. Créez une nouvelle classe ayant en héritage BlockFluidClassic :

    public class BlockFluidTutorial extends BlockFluidClassic
    {
        public BlockFluidTutorial(int id, Fluid fluid, Material material)
        {
            super(id, fluid, material);
        }
    }
    

    Ensuite nous allons déclarer deux icônes :

        private Icon stillIcon, flowingIcon;
    

    Maintenant nous allons ajoutez quelques fonctions :

        public Icon getIcon(int side, int meta)
        {
            return (side == 0 || side == 1) ? stillIcon : flowingIcon;
        }
    
        public void registerIcons(IconRegister register)
        {
            stillIcon = register.registerIcon("modtutoriel:tuto_fluid_still");
            flowingIcon = register.registerIcon("modtutoriel:tuto_fluid_flow");
        }
    
        public boolean canDisplace(IBlockAccess world, int x, int y, int z)
        {
            if(world.getBlockMaterial(x, y, z).isLiquid())
            {
                return false;
            }
            return super.canDisplace(world, x, y, z);
        }
    
        public boolean displaceIfPossible(World world, int x, int y, int z)
        {
            if(world.getBlockMaterial(x, y, z).isLiquid())
            {
                return false;
            }
            return super.displaceIfPossible(world, x, y, z);
        }
    

    getIcon, vous devrez connaitre, ensuite c'est juste une condition ternaire pour déterminer quel icône prendre.
    registerIcons, idem, vous devrez connaitre.
    canDisplace et displaceIfPossible servent à éviter que votre fluide coule dans les autres fluides.

    Les fichiers .png ne font pas 16x16, prenez celui de l'eau ou de la lave en exemple, ainsi que les fichiers .mcdata.
    Les fichiers .mcdata doivent être nommés comme la texture avec le .png. Exemple :
    tuto_fluid_still.png.mcdata

    {
        "animation": {
        "frametime": 1
        }
    }
    

    tuto_fluid_flow.png.mcdata

    {
        "animation": {}
    }
    

    (il s'agit du même contenu que l'eau)
    Plus le frametime et enlevé, plus l’animation est lente. Le tuto_fluid_flow n'a pas de frametime, il a donc la vitesse maximum.
    Je suis pas un pro en resourcepack, ce que je vous dit est ce que j'ai constaté par test, je suis pas sûr à 100 % que ce soit ça.
    Pour ralentir d'avantage, vous pouvez jouer sur les frames, prenez exemple sur lava_still.png.json

    La classe du seau

    Ici ça va allez très vite, faite simplement une nouvelle classe extends ItemBucket :

    package tutoriel.common;
    
    import net.minecraft.item.ItemBucket;
    
    public class ItemBucketTutorial extends ItemBucket
    {
        public ItemBucketTutorial(int id, int fluidId)
        {
            super(id, fluidId);
            this.setCreativeTab(ModTutoriel.TutorialCreativeTabs);
        }
    }
    

    Voila, rien à changer. Votre seau se videra mais ne pourra pas être remplit, pour ça il faut utiliser un event.

    Les events

    Comme dit plus haut, je préfère faire deux classes, donc je vais mettre onBucketFill dans BucketEvent et onPostTextureStitch dans TextureEvent, mais si vous avez créer une seul classe FluidEvent, cela fonctionne aussi.

    L'event du seau :

        @ForgeSubscribe
        public void onBucketFill(FillBucketEvent event)
        {
            int id = event.world.getBlockId(event.target.blockX, event.target.blockY, event.target.blockZ);
            int metadata = event.world.getBlockMetadata(event.target.blockX, event.target.blockY, event.target.blockZ);
            if(id == ModTutoriel.blockFluidTutorial.blockID && metadata == 0)
            {
                event.world.setBlockToAir(event.target.blockX, event.target.blockY, event.target.blockZ);
                event.result = new ItemStack(ModTutoriel.bucketTutorial);
                event.setResult(Result.ALLOW);
            }
        }
    

    @ForgeSubscribe pour indiquer que cette méthode suit les event de forge.
    Ensuite on récupère l'id et le medatada du bloc sur lequel le joueur à fait un clic droit avec un seau (cette event est déclenché lorsque le joueur fait un clic droit avec le seau)
    On vérifie que l'id correspond à notre bloc associer au fluide, et que son metadata est 0 (car seul la source à pour metadata 0, les autres blocs on un autres métadata).
    Si c'est le cas, on met de l'air sur le bloc, défini notre seau comme la valeur du résultat, puis on met sur le résultat de l'event sur true.

    L'event de la texture :

        @ForgeSubscribe
        @SideOnly(Side.CLIENT)
        public void onPostTextureStitch(TextureStitchEvent.Post event)
        {
            if(event.map.textureType == 0)
            {
                ModTutoriel.fluidTutorial.setIcons(ModTutoriel.blockFluidTutorial.getBlockTextureFromSide(1), ModTutoriel.blockFluidTutorial.getBlockTextureFromSide(2));
            }
        }
    

    Cette méthode défini la texture de notre fluide (pas celle du bloc, mais celle qu'on peut voir avec NEI plugins dans la catégorie FluidRegistry ou dans le gui des réservoirs de railcraft)
    Pensez bien au @SideOnly(Side.CLIENT) le code ne doit pas être exécuté en server side.
    Si la map de texture est 0 (les fluides et les blocs utilise la map de texture 0, et les items la 1), alors on enregistre nos icônes, le premier correspond à l'icône "Still" et le deuxième à l'icône "Flow"

    Les fichiers de lang et rendu final

    Un dernier truc à modifier, ce sont vos fichiers de langage.
    Pour le seau et le bloc, même chose que d'habitude : item/tile.<le nom non localisé>.name
    Par contre pour le fluide, pas de .name : fluid.<le nom non localisé>. Le nom du fluide est aussi seulement aperçu dans les tank railcraft et dans NEI, il n'a aucun rapport avec le nom du bloc.

    tile.fluidTutorial.name=Tutorial Fuild
    fluid.tutorial=Tutorial Fuild
    item.bucketTutorial.name=Tutorial Bucket
    

    Voir sur github + second commit correctif

    Questions / Réponses

    Rien pour l'instant.



  • Merci 😄
    depuis le temps que je l'attends je teste sa tout de suite !



  • Les tutos grandissent, on a de tout ! 😄



  • UP

    setDensity défini la densité du fluide, en quelque sorte sa masse. Si la valeur est négatif, votre fluide sera plus léger que l'air et s'envolera.

    Ne fonctionne pas… 😞


  • Administrateurs

    Ça marche très bien chez moi pourtant :



  • Mddr le fluide qui vole xD


  • Administrateurs

    Le fluide aussi quand il meurt il va au ciel. :C



  • @'robin4002':

    Ça marche très bien chez moi pourtant :

    Euh… Tu as mis quoi en Density et en Viscosity?


  • Administrateurs

    Même valeur que dans le tutoriel, j'ai juste mit un - devant le 4000 de la densité.


  • Administrateurs

    Petit fail de ma part, j'ai oublié le :
    .setContainerItem(Item.bucketEmpty)
    Dans l'initialisation du seau, du coup le seau était consumé quand on utilisait le seau sur un réservoir de buildcraft ou d'un autre mod, cela ne redonnait pas le seau vide :s
    Pensez donc à ajouter cette ligne pour ceux qui ont suivis ce tutoriel avant ce message !



  • Okay merci 🙂



  • Bonjour,
    J'ai suivi le tuto à la lettre, mais, quand je place le fluide, il est invisible ! (et en plus ça fait lagguer mon pc à mort, je cite : [Avertissement] [Minecraft-Client] Memory connection overburdened; after processing 2500 packets, we still have 48492 to go!)
    (et 2eme en plus, ça détruit tous les troncs d'arbres dans la zone chargée)

    Pouvez-vous m'aider ?

    Code :
    Spacecraft.java :

    
    package fr.MrBlockTNT.Spacecraft.core;
    
    import net.minecraft.block.Block;
    import net.minecraft.block.material.Material;
    import net.minecraft.item.Item;
    import net.minecraft.item.ItemStack;
    import net.minecraftforge.common.Configuration;
    import net.minecraftforge.common.MinecraftForge;
    import net.minecraftforge.fluids.Fluid;
    import net.minecraftforge.fluids.FluidContainerRegistry;
    import net.minecraftforge.fluids.FluidRegistry;
    import cpw.mods.fml.common.Mod;
    import cpw.mods.fml.common.Mod.EventHandler;
    import cpw.mods.fml.common.Mod.Instance;
    import cpw.mods.fml.common.SidedProxy;
    import cpw.mods.fml.common.event.FMLInitializationEvent;
    import cpw.mods.fml.common.event.FMLPostInitializationEvent;
    import cpw.mods.fml.common.event.FMLPreInitializationEvent;
    import cpw.mods.fml.common.network.NetworkMod;
    import cpw.mods.fml.common.registry.GameRegistry;
    import fr.MrBlockTNT.Spacecraft.proxy.CommonProxy;
    
    @Mod(modid = "Spacecraft", name = "Spacecraft", version = "1.0.0")
    @NetworkMod(clientSideRequired = true, serverSideRequired = false)
    
    public class Spacecraft
    {
    
    @Instance("Spacecraft")
    public static Spacecraft instance;
    
    @SidedProxy(clientSide = "fr.MrBlockTNT.Spacecraft.proxy.ClientProxy", serverSide = "fr.MrBlockTNT.Spacecraft.proxy.CommonProxy")
    public static CommonProxy proxy;
    
    public static Fluid oil;
    public static Block blockOil;
    public static Item bucketOil;
    public static int oilID, bucketOilID;
    
    @EventHandler
    public void PreInit(FMLPreInitializationEvent event)
    {
    //Configuration
    Configuration cfg = new Configuration(event.getSuggestedConfigurationFile());
    try
    {
    cfg.load();
    oilID = cfg.getBlock("Oil", 5000).getInt();
    bucketOilID = cfg.getBlock("Oil Bucket", 5001).getInt();
    }
    catch(Exception ex)
    {
    event.getModLog().severe("Failed to load configuration");
    }
    finally
    {
    if(cfg.hasChanged())
    {
    cfg.save();
    }
    }
    
    // Fluid
    oil = new Fluid("oil").setDensity(1500).setViscosity(200).setTemperature(273).setLuminosity(10).setUnlocalizedName("liquidNitrogen");
    FluidRegistry.registerFluid(oil);
    oil = FluidRegistry.getFluid("oil");
    
    // Blocks
    if(oil.getBlockID() == -1)
    {
    blockOil = new BlockOil(oilID, oil, Material.water).setUnlocalizedName("oilBlock");
    GameRegistry.registerBlock(blockOil, "oil");
    oil.setBlockID(blockOil);
    }
    else
    {
    blockOil = Block.blocksList[oil.getBlockID()];
    }
    
    // Items
    bucketOil = new ItemBucketOil(bucketOilID, blockOil.blockID).setUnlocalizedName("bucketOil").setTextureName("spacecraftcore:bucket_oil").setContainerItem(Item.bucketEmpty);
    GameRegistry.registerItem(bucketOil, "BucketOil", "Spacecraft");
    
    // Registry
    FluidContainerRegistry.registerFluidContainer(FluidRegistry.getFluidStack("oil", FluidContainerRegistry.BUCKET_VOLUME), new ItemStack(bucketOil), FluidContainerRegistry.EMPTY_BUCKET);
    MinecraftForge.EVENT_BUS.register(new TextureEvent());
    MinecraftForge.EVENT_BUS.register(new BucketEvent());
    }
    
    @EventHandler
    public void Init(FMLInitializationEvent event)
    {
    
    }
    
    @EventHandler
    public void PostInit(FMLPostInitializationEvent event)
    {
    
    }
    }
    
    

    TextureEvent.java :

    
    package fr.MrBlockTNT.Spacecraft.core;
    
    import cpw.mods.fml.relauncher.Side;
    import cpw.mods.fml.relauncher.SideOnly;
    import net.minecraftforge.client.event.TextureStitchEvent;
    import net.minecraftforge.event.ForgeSubscribe;
    
    public class TextureEvent
    {
    @ForgeSubscribe
    @SideOnly(Side.CLIENT)
    public void onPostTextureStitch(TextureStitchEvent.Post event)
    {
    if(event.map.textureType == 0)
    {
    Spacecraft.oil.setIcons(Spacecraft.blockOil.getBlockTextureFromSide(1), Spacecraft.blockOil.getBlockTextureFromSide(2));
    }
    }
    }
    
    

    ItemBucketOil.java :

    
    package fr.MrBlockTNT.Spacecraft.core;
    
    import net.minecraft.creativetab.CreativeTabs;
    import net.minecraft.item.ItemBucket;
    
    public class ItemBucketOil extends ItemBucket
    {
    public ItemBucketOil(int id, int fluidId)
    {
    super(id, fluidId);
    this.setCreativeTab(CreativeTabs.tabMisc);
    }
    }
    
    

    BucketEvent.java :

    
    package fr.MrBlockTNT.Spacecraft.core;
    
    import net.minecraft.item.ItemStack;
    import net.minecraftforge.event.Event.Result;
    import net.minecraftforge.event.ForgeSubscribe;
    import net.minecraftforge.event.entity.player.FillBucketEvent;
    
    public class BucketEvent
    {
    @ForgeSubscribe
    public void onBucketFill(FillBucketEvent event)
    {
    int id = event.world.getBlockId(event.target.blockX, event.target.blockY, event.target.blockZ);
    int metadata = event.world.getBlockMetadata(event.target.blockX, event.target.blockY, event.target.blockZ);
    if(id == Spacecraft.blockOil.blockID && metadata == 0)
    {
    event.world.setBlockToAir(event.target.blockX, event.target.blockY, event.target.blockZ);
    event.result = new ItemStack(Spacecraft.bucketOil);
    event.setResult(Result.ALLOW);
    }
    }
    }
    
    

    BlockOil.java :

    
    package fr.MrBlockTNT.Spacecraft.core;
    
    import net.minecraft.block.material.Material;
    import net.minecraft.client.renderer.texture.IconRegister;
    import net.minecraft.util.Icon;
    import net.minecraft.world.IBlockAccess;
    import net.minecraft.world.World;
    import net.minecraftforge.fluids.BlockFluidClassic;
    import net.minecraftforge.fluids.Fluid;
    
    public class BlockOil extends BlockFluidClassic
    {
    private Icon stillIcon, flowingIcon;
    
    public BlockOil(int id, Fluid fluid, Material material)
    {
    super(id, fluid, material);
    }
    
    public Icon getIcon(int side, int meta)
    {
    return (side == 0 || side == 1) ? stillIcon : flowingIcon;
    }
    
    public void registerIcons(IconRegister register)
    {
    stillIcon = register.registerIcon("spacecraftcore:oil");
    flowingIcon = register.registerIcon("spacecraftcore:oil_flow");
    }
    
    public boolean canDisplace(IBlockAccess world, int x, int y, int z)
    {
    if(world.getBlockMaterial(x, y, z).isLiquid())
    {
    return false;
    }
    return super.canDisplace(world, x, y, z);
    }
    
    public boolean displaceIfPossible(World world, int x, int y, int z)
    {
    if(world.getBlockMaterial(x, y, z).isLiquid())
    {
    return false;
    }
    return super.displaceIfPossible(world, x, y, z);
    }
    }
    
    

  • Administrateurs

    @'MrBlockTNT':

    […]
    oilID = cfg.getBlock("Oil", 5000).getInt();
    […]
    

    Retour à la base de la création d'un bloc :
    @'robin4002':

    (les ids de bloc vont de 0 à 4095, les ids de 0 à 600 sont à éviter pour ne pas avoir de conflit avec les id de minecraft),

    C'est vraiment bête comme erreur 😕



  • Ah oui désolé j'avais pas vu x)



  • j'ai un crash au lancement du jeu
    Invalid FluidContainerData - a parameter was null.

    dans le crash report il cite cette ligne :

    
    FluidContainerRegistry.registerFluidContainer(FluidRegistry.getFluidStack("helium3", FluidContainerRegistry.BUCKET_VOLUME), new ItemStack(bucketHelium3), FluidContainerRegistry.EMPTY_BUCKET);
    
    


  • Je pense que bucketHelium3 est null, je peux voir la ligne où il est déclaré?



  • 
    public static Item bucketHelium3;
    
    

    sa ?



  • L'as-tu initialisé avec :

    bucketHelium3 = new ItemPerso().setUnlocalizedName("").setTextureName("");
    


  • 
    bucketHelium3 = new BucketHelium3(1019, BlockHelium3.blockID).setUnlocalizedName("bucketh3").setTextureName("universe:bucketTutorial").setContainerItem(Item.bucketEmpty);
    
    

    oui il y est


  • Administrateurs

    Code total, on a besoin de savoir dans quel ordre tu as initialisé chaque truc.