24 mai 2014, 12:03

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