20 févr. 2016, 22:55

Sommaire

Introduction

Bonjour, dans ce tutoriel, je vais vous montrer comment utiliser le nouveau système de “capabilities” ou capacité qui remplace les Extended Entity Properties des versions précédentes. Les capacités ont la même fonction que les EEP, elles servent à sauvegarder des données “dans” une entité. NB: Le système de Extended Entity Properties est déprécié depuis la 1.8.

Pré-requis

Code

Packet :

Je vous invite donc à créer une nouvelle classe pour notre Packet, je vais la nommer “PacketCapabilitiesTutoriel” pour ce tutoriel. Vous devez implémenter IMessage dans votre classe et ajouter les méthodes non-implémentées. Ça devrait vous donner ça:

public class PacketCapabilitiesTutoriel implements IMessage
{
    @Override
    public void fromBytes(ByteBuf buf)
    {

    }

    @Override
    public void toBytes(ByteBuf buf)
    {

    }
}

Pour cet exemple, je vais faire un système de monnaie comme Gugu42 a fait dans son tutoriel sur les Extended Entity Properties. Donc, on crée une nouvelle variable public de type int en haut de notre classe. Ensuite, à l’aide du ByteBuf, on la convertie en byte et on la récupère, il ne faut pas oublier de créer un constructeur vide et un constructeur avec les valeurs nécessaires sinon ça ne fonctionnera pas.

public int money;

public PacketCapabilitiesTutoriel(int money)
{
        this.money = money;
}

public PacketCapabilitiesTutoriel() {}

@Override
public void fromBytes(ByteBuf buf)
{
    this.money = buf.readInt();
}

@Override
public void toBytes(ByteBuf buf)
{
    buf.writeInt(this.money);
}

Il ne nous manque plus que les Handlers, c’est relativement simple, on crée deux classes statiques à l’intérieur de notre classe. Les handlers doivent implémenter IMessageHandler, le premier sera nommé ClientHandler et le deuxième ServerHandler.

public static class ServerHandler implements IMessageHandler <PacketCapabilitiesTutoriel, IMessage>{

    @Override
    public IMessage onMessage(PacketCapabilitiesTutoriel message, MessageContext ctx)
    {
        //Nous reviendrons sur cette ligne plus tard.
        MinecraftServer.getServer().addScheduledTask(new ScheduledPacketTask(ctx.getServerHandler().playerEntity, message));
        return null;
    }

}

@SideOnly(Side.CLIENT)
public static class ClientHandler implements IMessageHandler <PacketCapabilitiesTutoriel, IMessage>{

    @Override
    public IMessage onMessage(PacketCapabilitiesTutoriel message, MessageContext ctx)
    {
        //Nous reviendrons sur cette ligne plus tard.
        Minecraft.getMinecraft().addScheduledTask(new ScheduledPacketTask(null, message));
        return null;
    }
}

Classe Principale :

Il y a plusieurs choses à faire ici. Premièrement, vous devez enregistrer le packet:

network.registerMessage(PacketCapabilitiesTutoriel.ClientHandler.class, PacketCapabilitiesTutoriel.class, 3, Side.CLIENT);
network.registerMessage(PacketCapabilitiesTutoriel.ServerHandler.class, PacketCapabilitiesTutoriel.class, 3, Side.SERVER);

Ensuite, vous devez ajouter le code ci-dessous en haut de votre classe.

    @CapabilityInject(TutoCapabilities.class)
    public static final Capability<TutoCapabilities> TUTO_CAP = null;

Et finalement, vous devez enregistrer votre capacité dans le init:

TutoCapabilities.register();

ScheduledPacketTask:

J’ai créé cette classe pour rendre mon code plus lisible, mais vous pouvez très bien la mettre directement dans vos paramètres comme n’importe quel “runnable”. Implémentez Runnable dans votre classe et ajoutez les méthodes non-implémentées. Vous devez ensuite créer un constructeur avec les paramètres nécessaires. Pour la rendre compatible client et serveur, j’ai mis un paramètre EntityPlayer, il sera null pour le client et défini pour le serveur. Ça devrait donner ça:

public class ScheduledPacketTask implements Runnable
{
    private EntityPlayer player;
    private PacketCapabilitiesTutoriel message;

    public ScheduledPacketTask(EntityPlayer player, PacketCapabilitiesTutoriel message)
    {
        this.player = player;
        this.message = message;
    }

    @Override
    public void run()
    {
        //Condition ternaire pour récupérer le joueur selon le côté.
        EntityPlayer player = this.player == null ? getPlayer() : this.player;
        //On revient sur cette ligne plus tard.
        player.getCapability(ModTutoriel.TUTO_CAP, null).setMoney(message.money);
    }

    @SideOnly(Side.CLIENT)
    private EntityPlayer getPlayer()
    {
        return Minecraft.getMinecraft().thePlayer;
    }

}

Le provider, le storage et la factory :

Je vous invite maintenant à créer une classe qui implémente ICapabilityProvider et INBTSerializable. Ajoutez les méthodes non-implémentées et ça devrait vous donner ceci:

public class TutoCapabilities implements ICapabilityProvider, INBTSerializable<NBTTagCompound> {
    @Override
    public boolean hasCapability(Capability capability, EnumFacing facing)
    {
        return false;
    }

    @Override
    public <T> T getCapability(Capability <T>capability, EnumFacing facing)
    {
        return null;
    }

    @Override
    public NBTTagCompound serializeNBT()
    {
        return null;
    }

    @Override
    public void deserializeNBT(NBTTagCompound compound)
    {

    }
}

Maintenant, nous allons modifier les méthodes hasCapability et getCapability pour les adapter à notre code et pour qu’elles retournent les bonnes valeurs. Premièrement, on doit modifier le return de hasCapability pour que ça retourne la véritable valeur et non simplement false.

return ModTutoriel.TUTO_CAP != null && capability == ModTutoriel.TUTO_CAP;

On vérifie que la capacité ne soit pas nulle et que ce soit bien la nôtre. Si oui, on retourne true, sinon on retourne false. Ensuite, on change le return de getCapability pour retourner l’instance de la classe en faisant les mêmes vérifications que plus haut.

return ModTutoriel.TUTO_CAP != null && capability == ModTutoriel.TUTO_CAP ? (T)this : null;

Ensuite, il faut modifier les méthodes deserializeNBT et serializeNBT, elles servent à sauvegarder les valeurs. Dans le deserializeNBT, nous devons créer un nouvel objet NBTTagCompound, qui va stocker nos valeurs. Ensuite, on doit ajouter notre valeur dans le compound et finalement, on le retourne.

@Override
public NBTTagCompound serializeNBT()
{
    NBTTagCompound compound = new NBTTagCompound();
    compound.setInteger("Money", this.getMoney());
    return compound;
}

Et dans la méthode deserialize, on récupère le tag et on met à jour la valeur avec celui-ci.

@Override
public void deserializeNBT(NBTTagCompound compound)
{
    this.setMoney(compound.getInteger("Money"));
}

Après, on crée la variable public de type int et on crée un getter/setter pour celle-ci.

public int money;

public void setMoney(int money)
{
    this.money = money;
}

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

Maintenant, on va créer deux classes statiques dans notre classe comme avec les packets. Les deux sont inutiles, mais elles sont nécessaires pour que cela fonctionne. NB: Elles ne sont pas inutiles, mais elles ne nous servent pas. Elles servent seulement quand l’entité n’appartient pas à Minecraft.

public static class Storage implements Capability.IStorage<TutoCapabilities> {

    @Override
    public NBTBase writeNBT(Capability<TutoCapabilities> capability, TutoCapabilities instance, EnumFacing side)
    {
        return null;
    }

    @Override
    public void readNBT(Capability<TutoCapabilities> capability, TutoCapabilities instance, EnumFacing side, NBTBase nbt)
    {

    }

}

public static class Factory implements Callable<TutoCapabilities> {
    @Override
    public TutoCapabilities call() throws Exception
    {
        return null;
    }
}

Finalement, nous devons créer deux méthodes: sync, register et un constructeur. La méthode sync sert à synchroniser entre le client et le serveur, elle doit être appelée quand on modifie une valeur. La méthode register, elle, doit être statique et est appelée depuis la classe principale, elle sert à enregistrer la capacité. Le constructeur, lui, est appelé quand on attache la capacité au joueur. Il faut aussi créer une nouvelle variable de type EntityPlayer pour pouvoir récupérer le joueur dans notre méthode sync.

private EntityPlayer player;

public static void register()
{
    CapabilityManager.INSTANCE.register(TutoCapabilities.class, new TutoCapabilities.Storage(), new TutoCapabilities.Factory());
}

public TutoCapabilities(EntityPlayer player)
{
    this.money = 0;
    this.player = player;
}

public void sync()
{
    PacketCapabilitiesTutoriel packet = new PacketCapabilitiesTutoriel(this.getMoney());
    if(!this.player.worldObj.isRemote)
    {
        EntityPlayerMP playerMP = (EntityPlayerMP)player;
        ModTutoriel.network.sendTo(packet, playerMP);
    }
    else
    {
        ModTutoriel.network.sendToServer(packet);
    }
}

L’event handler :

Je ne décrirai pas comment enregistrer un event handler, je vais seulement expliquer les events à utiliser. Si vous ne savez pas comment utiliser les events, je vous invite à aller voir le tutoriel sur ceux-ci dans les pré-requis. Le premier event à utiliser est le PlayerEvent.Clone, il est appelé quand le joueur est cloné. Le joueur est cloné quand il change de dimension et quand il meurt. Dans notre cas, on veut exécuter l’event seulement à la mort du joueur. Dans l’event, vous avez accès à plusieurs variables: original, entityPlayer et wasDeath. Nous aurons besoin des trois, le premier est l’instance du joueur qui est mort, le deuxième est l’instance du nouveau joueur qui respawn et le dernier est un boolean qui est à true si l’event est appelé à la suite de la mort du joueur.

    @SubscribeEvent
    public void onPlayerCloned(PlayerEvent.Clone event)
    {
        if(event.wasDeath)
        {
            if(event.original.hasCapability(ModTutoriel.TUTO_CAP, null))
            {
                TutoCapabilities cap = event.original.getCapability(ModTutoriel.TUTO_CAP, null);
                TutoCapabilities newCap = event.entityPlayer.getCapability(ModTutoriel.TUTO_CAP, null);
                newCap.setMoney(cap.getMoney());
            }
        }
    }

Le deuxième event à utiliser est le PlayerRespawnEvent, il est appelé quand le joueur respawn, mais après le PlayerEvent.Clone et le prochain event. Dans cet event, nous allons synchroniser, tout simplement.

@SubscribeEvent
public void onPlayerRespawn(PlayerRespawnEvent event) 
{
    if(!event.player.worldObj.isRemote)
    {
        event.player.getCapability(ModTutoriel.TUTO_CAP, null).sync();
    }
}

Et finalement, le dernier event à utiliser est le AttachCapabilitiesEvent.Entity, il est appelé quand les autres capacités sont attachées aux entités. C’est à ce moment que l’on doit attacher notre capacité à notre joueur.

@SubscribeEvent
public void onAttachCapability(AttachCapabilitiesEvent.Entity event)
{
    if(event.getEntity() instanceof EntityPlayer)
    {
        event.addCapability(new ResourceLocation(ModTutoriel.MODID + ":TUTO_CAP"), new TutoCapabilities((EntityPlayer) event.getEntity()));
    }
}

Bonus

Aucun bonus pour le moment, mais n’hésitez pas à le demander si vous avez une idée.

Résultat

Je ne peux pas prendre de screenshot du résultat, puisque le but est de sauvegarder des données. Le résultat est simplement que les données sont sauvegardées après la mort et après la déconnexion.

Crédits

Rédaction :

Correction :

Mentions spéciales :

  • diesieben07
  • Lexmanos

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