Extended Entity Properties



  • Sommaire

    Introduction

    Les Extended Entity Properties (que nous appellerons Propriétés d'entité) permettent d'ajouter de manière modulable des informations à une entité.
    Leurs utilisations sont nombreuses.
    Par exemple, le mod StarMiner (mod Japonais) qui en utilise pour stocker l'état de gravité du joueur, ou même mon propre mod, Ratchet & Clank mod, qui en utilise pour stocker une monnaie.

    Dans ce tutoriel, nous verrons principalement un système de monnaie, mais les modifications pour en faire un système de mana sont très simples.

    #Pré-requis(Pré-requis)

    Pour ce tutoriel, vous devez savoir manipuler les packets, et avoir une classe d'events.
    Je conseille aussi d'avoir un item pour tester votre propriété (Un item click droit qui vous dis votre argent, étant donné que ce tutoriel ne parleras pas de comment faire un GUI).

    Code

    Classe ExtendedProperties :

    La classe ExtendedProperties va gérer tout ce qui touche à votre propriété. Si c'est de l'argent, on peut faire des méthodes pour en retirer, en ajouter, ou payer ( Return d'un boolean disant si le joueur possède l'argent, tout en consommant celui-ci ).

    Pour commencer, nous allons simplement faire une classe :

    public class ExtendedEntityPropTuto implements IExtendedEntityProperties {
        @Override
        public void saveNBTData(NBTTagCompound compound) {
            // TODO Auto-generated method stub
        }
    
        @Override
        public void loadNBTData(NBTTagCompound compound) {
            // TODO Auto-generated method stub
        }
    
        @Override
        public void init(Entity entity, World world) {
            // TODO Auto-generated method stub
        }
    
    }
    

    Les méthodes seront générées automatiquement quand vous implémenterez la classe IExtendedEntityProperties.

    Maintenant, nous allons ajouter un identifiant à la propriété, ainsi que les variables qu'elle utilisera. Dans notre cas, le joueur cible, de l'argent, et une limite d'argent.

        public final static String EXT_PROP_NAME = "ExtPropTuto";
    
        private final EntityPlayer player;
    
        public long money;
        public long maxMoney;
    

    L'utilisation d'un long permet un stockage de grande taille pour votre argent. Si vous ne comptez pas atteindre plus de 2.147.483.647 d'argent, utilisez un int.

    On ajoute ensuite le constructeur. Il prend en paramètre le player auquel nous allons toucher.

    public ExtendedEntityPropTuto(EntityPlayer player) {
            this.player = player;
            this.money = 0;
            this.maxMoney = {Votre limite};
        }
    

    Maintenant, nous allons faire deux méthodes. Celles-ci vont ajouter les propriétés au joueur, et les obtenir, pour avoir son état actuel.

            public static final void register(EntityPlayer player) {
            player.registerExtendedProperties(ExtendedEntityPropTuto.EXT_PROP_NAME,
                    new ExtendedEntityPropTuto(player));
        }
    
        public static final ExtendedEntityPropTuto get(EntityPlayer player) {
            return (ExtendedEntityPropTuto) player.getExtendedProperties(EXT_PROP_NAME);
        }
    

    Simple n'est-ce pas ? La méthode get sera utilisée très souvent, dès que vous touchez à un player.

    Nous allons maintenant remplir les deux méthodes concernant la sauvegarde et l'obtention de ces données :

        @Override
        public void saveNBTData(NBTTagCompound compound) {
    
            NBTTagCompound properties = new NBTTagCompound();
    
            properties.setLong("Money", this.money);
            properties.setLong("MaxMoney", this.maxMoney);
    
            compound.setTag(EXT_PROP_NAME, properties);
        }
    
        @Override
        public void loadNBTData(NBTTagCompound compound) {
            NBTTagCompound properties = (NBTTagCompound) compound
                    .getTag(EXT_PROP_NAME);
            this.money = properties.getLong("Money");
            this.maxMoney = properties.getLong("MaxMoney");
        }
    

    La base est déjà là. On peut stocker des données dans notre joueur !

    Maintenant, nous arrivons à une partie assez lourde : La synchronisation client / serveur.
    Pour cela, il faudra que vous créez un packet qui enverra les informations.
    J'utilise FFMT API pour mes packets, et je vous le conseille.
    Je ne peux que donner ma méthode, et vous verrez ma classe packet un peu plus loin dans le tutoriel.

    public final void sync() {
            PacketMoney packetMoney = new PacketMoney(this.maxMoney, this.money);
                //La ligne suivante dépend de votre manière d'envoyer les packets. Celle-ci vient de mon mod, je ne la changerais pas car je ne peux l'appliquer à votre mod, mais elle reste bonne pour un exemple.
            RcMod.rcModPacketHandler.sendToServer(packetMoney);
    
            if (!player.worldObj.isRemote) {
                EntityPlayerMP player1 = (EntityPlayerMP) player;
                            //Ici, même chose que précédemment, sauf que le packet est envoyé au player.
                RcMod.rcModPacketHandler.sendTo(packetMoney, player1);
            }
        }
    

    Cette fonction reste assez simple. On crée un packet avec les informations et on l'envoie.

    Encore une méthode de sauvegarde :

    private static String getSaveKey(EntityPlayer player) {
            return player.getDisplayName() + ":" + EXT_PROP_NAME;
        }
    

    Des méthodes utilitaires, qui seront utilisées dans l'event handler :

        public static void saveProxyData(EntityPlayer player) {
            ExtendedEntityPropTuto playerData = ExtendedEntityPropTuto.get(player);
            NBTTagCompound savedData = new NBTTagCompound();
    
            playerData.saveNBTData(savedData);
            CommonProxy.storeEntityData(getSaveKey(player), savedData);
        }
    
        public static void loadProxyData(EntityPlayer player) {
            ExtendedEntityPropTuto playerData = ExtendedEntityPropTuto.get(player);
            NBTTagCompound savedData = CommonProxy
                    .getEntityData(getSaveKey(player));
    
            if (savedData != null) {
                playerData.loadNBTData(savedData);
            }
            playerData.sync();
        }
    

    Celles-ci servent, une fois de plus, à la sauvegarde et à la lecture des données.

    Voilà ! Les méthodes génériques sont terminées, et on peut maintenant passer à ce qui nous intéresse : Les méthodes spécifiques. Dans notre cas, nous voulons plusieurs méthodes :
    Une pour ajouter de l'argent, une pour en retirer, une deuxième méthode pour en retirer, qui aura un fonctionnement particulier : Elle vérifiera que le player possède assez d'argent lors du retrait, et retournera true si le retrait a bien été effectué. C'est le principe du paiement : Si vous avez assez d'argent, l'action que vous comptez faire est valide, et on prend l'argent.
    Une autre méthode peut-être utile, pour avoir une commande qui met votre argent à une certaine valeur. Par exemple, pour vos tests, vous pouvez faire /argent 1000 pour avoir 1000 d'argent etc...

    Je les donnes à la suite, car elle sont relativement simples :

    public boolean pay(long amount) {
            boolean sufficient = amount <= this.money;
    
            if (sufficient) {
                this.money -= amount;
                this.sync();
            } else {
                return false;
            }
    
            return sufficient;
        }
    
        public void addMoney(long amount) {
            this.money += amount;
            this.sync();
        }
    
        public long getMoney() {
            return this.money;
        }
    
        public void setMoney(long newMoney) {
            this.money = newMoney;
            this.sync();
        }
    

    Comme vous le remarquez, après chaque changement, on synchronise.

    C'est tout pour la classe des propriétés. On peut y ajouter bien d'autres fonctions, le système d'argent est juste simple à comprendre.

    EventHandler :

    Dans votre event handler, vous allez avoir besoin de pas mal d'events.
    Nous devons obtenir la propriété très souvent : quand l'entité quitte le monde, rejoint le monde, meurt etc..
    Je donne les méthodes directement, car il n'y a pas de réel intérêt à les expliquer :

        @SubscribeEvent
        public void onEntityConstructing(EntityConstructing event) {
    
            if (event.entity instanceof EntityPlayer
                    && ExtendedEntityPropTuto.get((EntityPlayer) event.entity) == null)
    
                ExtendedEntityPropTuto.register((EntityPlayer) event.entity);
        }
    
    @SubscribeEvent
        public void onLivingDeathEvent(LivingDeathEvent event) {
            if (!event.entity.worldObj.isRemote
                    && event.entity instanceof EntityPlayer) {
                NBTTagCompound playerData = new NBTTagCompound();
                ((ExtendedEntityPropTuto) (event.entity
                        .getExtendedProperties(ExtendedEntityPropTuto.EXT_PROP_NAME)))
                        .saveNBTData(playerData);
                proxy.storeEntityData(
                        ((EntityPlayer) event.entity).getDisplayName(), playerData);
                ExtendedEntityPropTuto.saveProxyData((EntityPlayer) event.entity);
            } else {
    
            }
        }
    

    Cette méthode permet de conserver les données après la mort. Dans le cas du mana, elle n'est pas nécessaire si vous souhaitez que le joueur reparte avec 0 de mana. Si votre maximum de mana peut varier, conservez la.

    @SubscribeEvent
        public void onEntityJoinWorld(EntityJoinWorldEvent event) {
            if (!event.entity.worldObj.isRemote && event.entity instanceof EntityPlayer) {
                NBTTagCompound playerData = proxy
                        .getEntityData(((EntityPlayer) event.entity)
                                .getDisplayName());
                if (playerData != null) {
                    ((ExtendedEntityPropTuto) (event.entity
                            .getExtendedProperties(ExtendedEntityPropTuto.EXT_PROP_NAME)))
                            .loadNBTData(playerData);
                }
    
                ((ExtendedEntityPropTuto) (event.entity
                        .getExtendedProperties(ExtendedEntityPropTuto.EXT_PROP_NAME)))
                        .sync();
            }
        }
    

    Quand l'entité change de monde / se connecte.

    C'est tout pour l'event handler !

    Classe du packet :

    Le packet est ce qui va permettre au client et au serveur de se transmettre les données. Cette classe est propre à ma manière d'utiliser les packets. Il est possible que vous n'ayez pas à faire la même chose. Dans ce cas, inspirez-vous de ce que je fais.

    Note : Si vous souhaitez faire comme moi, vous devez avoir FFMT-Lib

    
    public class PacketMoney extends AbstractPacket{
    
        private long maxMoney, Money;
    
        public PacketMoney(){
    
        }
    
        public PacketMoney(long maxMoney, long money){
            this.maxMoney = maxMoney;
            this.money = money;
        }
    
        @Override
        public void encodeInto(ChannelHandlerContext ctx, ByteBuf buffer) {
            buffer.writeLong(maxMoney);
            buffer.writeLong(money);
    
        }
    
        @Override
        public void decodeInto(ChannelHandlerContext ctx, ByteBuf buffer) {
            this.maxMoney = buffer.readLong();
            this.money = buffer.readLong();
        }
    
        @Override
        public void handleClientSide(EntityPlayer player) {
            ExtendedEntityPropTuto props = ExtendedEntityPropTuto
                    .get(player);	
            props.maxMoney = this.maxMoney;
            props.money = this.money;
        }
    
        @Override
        public void handleServerSide(EntityPlayer player) {
            ExtendedEntityPropTuto props = ExtendedEntityPropTuto
                    .get(player);
            props.maxMoney = this.maxMoney;
            props.money = this.money;
        }
    }
    

    CommonProxy :

    Dans votre CommonProxy, vous devez ajouter ces deux méthodes :

    public static void storeEntityData(String name, NBTTagCompound compound) {
            extendedEntityData.put(name, compound);
        }
    
        public static NBTTagCompound getEntityData(String name) {
            return extendedEntityData.remove(name);
        }
    

    Vous allez avoir une erreur, c'est normal, le proxy n'as pas la variable extendedEntityData

    La voici :

    private static final Map <String, NBTTagCompound> extendedEntityData = new HashMap<String, NBTTagCompound>();
    

    Pensez bien a mettre tout ça dans le COMMON proxy ! C'est assez rare d'y mettre autre chose que des méthodes vides, mais mettez y bien dedans !

    Bonus

    Classe d'un Item :

        @Override
        public ItemStack onItemRightClick(ItemStack itemstack, World world,
                EntityPlayer player) {
            if (!world.isRemote) {
    
                ExtendedEntityPropTuto props = ExtendedEntityPropTuto.get(player);
    
                if (props.pay(15)) {
                    System.out
                            .println("Squalala, nous sommes partis!");
                } else {
                    System.out
                            .println("Pas d'argent ! Je suis triste !");
                }
            }
            return itemstack;
        }
    

    Un exemple d'utilisation :

    Résultat

    Le résultat n'est pas immédiatement visible. Vous aurez besoin de vous en servir dans un autre système !

    Crédits

    Rédaction :

    Correction :

    Creative Commons
    Ce tutoriel de Minecraft Forge France est mis à disposition selon les termes de la licence Creative Commons Attribution - Pas d’Utilisation Commerciale - Partage dans les Mêmes Conditions 4.0 International

    retourRetour vers le sommaire des tutoriels



  • Tutoriel sorti ! 🙂



  • Super Tuto !!! Je ne penses pas m'en servir pour le moment mais peut-être que par la suite…. 🙂



  • Génial gugu j'en aurai besoin pour la refonte de minecraft que j'effectue



  • Si vous avez besoin d'aide pour par exemple modifier le système, je suis disponible 😉



  • Merci beaucoup. Je vais pouvoir continuer mon mod plus rapidement que prévu finalement :)Merci de la vitesse à laquelle tu as fait ce tuto 🙂



  • J'ai une question: comment l'adapter pour que ça marche en 1.6.x??



  • Merci beaucoup gugu42! Sans toi je n'aurais jamais trouvé, avant qu'on ne me parle de ton tuto j'ai deja cherché une journée en vain xD



  • @'isador34':

    J'ai une question: comment l'adapter pour que ça marche en 1.6.x??

    Hm, essaie de faire la même chose qu'en 1.7 et envoie moi les erreurs. C'est la solution la plus simple.



  • => le lien de FFMT API est mort, je fait une erreur 404


  • Administrateurs

    http://mods.mcnanotech.fr/index.php/Accueil
    La doc n'est pas encore complète, mais si tu veux tu peux regarder sur nanotech mod : https://github.com/FFMT/nanotech_mod



  • Super j'en avais besoin pour mon mod 😄 Merci 🙂



  • bha gugu le problème c'est que tu utiliser des fonction de FFMT dans ton code hors, c'est fonction n'existait pas en 1.6.x…..
    ainsi que le AbstractPacket



  • Bah, tu a juste a envoyer un Packet qui contient les informations ^^

    https://github.com/Gugu42/RatchetAndClankMod/commit/16c2a6711e91a672bda4892db67f6cbdf6987ab2

    Ce commit contient la manière dont j'ai fait mes packets en 1.6.x



  • Merci 🙂



  • Il y a une petite erreur a ce niveau là (par rapport au fait de pouvoir tapez des commandes)

    public int getMoney() {
    return this.money;
    }
    

    Ici ce n'est pas un "public int getMoney()" mais plutôt un "public long getMoney()" étant donnée que juste au dessus on a déclarer la Money comme étant un "long"

    public final static String EXT_PROP_NAME = "ExtPropTuto";
    
    private final EntityPlayer player;
    
    public long money;
    public long maxMoney;
    

    C'est possible d'avoir un GitHub avec le codage au complet car j'ai du mal a situé ou doivent être tous les éléments indiquer dans le tuto ?

    merci d'avance 🙂



  • Heu pour le github, je n'en ai pas fait un, je me base sur celui de mon mod Ratchet & Clank ( http://github.com/Gugu42/ratchetandclankmod/ )



  • Le problème est que visiblement dans ce GitHub une ancienne version de "FFMT" car on utilise plus ceci :

    public class PacketRefill extends FFMTPacket {
    
    public void encodeInto(ChannelHandlerContext ctx, ByteBuf buffer) {
    
    public void decodeInto(ChannelHandlerContext ctx, ByteBuf buffer) {
    

    On utilise quelques choses de similaire a ça :

    public class PacketRefill extends AbstractPacket {
    
    public void writeData(ByteBuf buffer) throws IOException {
    
    public void readData(ByteBuf buffer) {
    

    Donc je peux pas vraiment m'en servir 😕 je regarde un peu plus car moi ce qui m'interesse dans le GitHub c'etait ou été placer les EVENT HANDLER.


    Je vois que sur le GitHub tu utilise une classe "RcEventHandler.java"

    Et que tu l'initialise ici :

    @EventHandler
    public void PostInit(FMLPostInitializationEvent event) {
    NetworkRegistry.INSTANCE.registerGuiHandler(this, new GuiHandler());
    MinecraftForge.EVENT_BUS.register(new RcEventHandler());
    

    C'est bien ça ?



  • Mais … c'est exactement la même chose, les méthodes sont juste renommées ._.



  • Oui oui ça moi je l'est compris mais début j'avais du mal … car j’étais débutant 😛 tu vois ce que je veux dire .. c'est en discutant avec vous et en posant des question qu'on comprend tous ça... ce qui est malheureux pour un tuto ou on devrait pas vous déranger ... surtout pour des choses si futile 😉