Capability Helpers

Attache des donnees personnalisees a n'importe quelle entite, item ou tile entity en une seule annotation. Fini les 6 fichiers boilerplate — une classe suffit.

fr.eri.eriapi.capability Minecraft 1.12.2 Forge Capabilities

Introduction

Le systeme de Capabilities de Forge est extremement puissant, mais sa mise en oeuvre manuelle est fastidieuse. Pour attacher la moindre donnee a un joueur, Forge te demande de creer :

  • Une interface definissant les getters/setters
  • Une implementation de cette interface
  • Un IStorage pour la serialisation NBT
  • Un ICapabilityProvider pour exposer la capability
  • Un event handler pour attacher le provider aux entites
  • Un packet reseau pour la synchronisation client

Le module Capability Helpers d'EriAPI reduit tout ca a une seule annotation placee sur une classe Java ordinaire (un POJO). Le framework genere automatiquement tout le code technique necessaire, et synchronise les champs marques @Sync avec le client sans aucun effort de ta part.

Principe cle

Tu ecris uniquement la donnee qui t'interesse. EriAPI s'occupe de tout le reste : enregistrement, serialisation NBT, gestion de la mort du joueur, et synchronisation reseau.

@EriCapability — Annotation

Place @EriCapability sur n'importe quelle classe Java pour la transformer en capability Forge. La classe doit avoir un constructeur sans arguments (ou aucun constructeur — Java en cree un par defaut). Les champs peuvent etre de n'importe quel type primitif, String, ou type serialisable en NBT.

@EriCapability
fr.eri.eriapi.capability.EriCapability
Annotation

Transforme une classe POJO en capability Forge enregistree et geree automatiquement.

  • String modId() L'identifiant de ton mod (ex. "mymod"). Utilise pour nommer la capability en interne.
  • AttachTarget target() Definit a quoi cette capability est attachee : PLAYER, ENTITY, ITEM, ou TILE_ENTITY.
  • boolean persistOnDeath() default true Si true, les donnees sont copiees lors de la mort et respawn du joueur (keepInventory ou non). Par defaut : true.
Java — Declarer une capability simple
import fr.eri.eriapi.capability.EriCapability;
import fr.eri.eriapi.capability.AttachTarget;
import fr.eri.eriapi.capability.Sync;

@EriCapability(modId = "mymod", target = AttachTarget.PLAYER)
public class Mana {
    @Sync public int current = 100;
    @Sync public int max = 100;
    public float regenRate = 0.5f; // non synce, cote serveur uniquement
}
Valeurs par defaut

Les valeurs que tu assignes dans la declaration des champs sont les valeurs initiales pour chaque nouveau joueur (ou entite). Elles sont utilisees a la premiere creation de la capability.

Exemple avec persistOnDeath desactive

Si tu veux que la donnee soit reinitalisee a la mort du joueur (comme des buffs temporaires), passe persistOnDeath = false.

Java — Capability qui se reinitialise a la mort
@EriCapability(modId = "mymod", target = AttachTarget.PLAYER, persistOnDeath = false)
public class TempBuffs {
    @Sync public boolean speedBoost = false;
    @Sync public int boostTicksRemaining = 0;
}

AttachTarget — Enum

AttachTarget determine sur quel type d'objet Minecraft la capability sera attachee. Choisis la valeur qui correspond a ce que tu veux stocker.

AttachTarget
fr.eri.eriapi.capability.AttachTarget
Enum
  • PLAYER Attache aux joueurs (EntityPlayer). Le cas d'usage le plus courant : mana, XP personnalise, statistiques, flags de quete.
  • ENTITY Attache a toute entite vivante (EntityLivingBase) : monstres, animaux, boss. Utile pour ajouter des donnees supplementaires aux mobs.
  • ITEM Attache a un ItemStack. Permet de stocker des donnees propres a une instance d'item (durabilite personnalisee, enchantements custom, charges).
  • TILE_ENTITY Attache a un TileEntity (bloc avec logique). Utile pour ajouter des donnees a des blocs existants sans les sous-classer.
Java — Exemples de cibles differentes
// Donnees attachees a un joueur
@EriCapability(modId = "myrpg", target = AttachTarget.PLAYER)
public class PlayerStats {
    @Sync public int level = 1;
    @Sync public int experience = 0;
}

// Donnees attachees a un item (ex. une epee avec des charges magiques)
@EriCapability(modId = "myrpg", target = AttachTarget.ITEM)
public class SoulBound {
    @Sync public boolean bound = false;
    @Sync public String ownerName = "";
}

// Donnees attachees a un bloc (tile entity)
@EriCapability(modId = "myrpg", target = AttachTarget.TILE_ENTITY)
public class MachineData {
    @Sync public int energy = 0;
    @Sync public boolean active = false;
}

// Donnees attachees a un monstre
@EriCapability(modId = "myrpg", target = AttachTarget.ENTITY)
public class BossPhase {
    @Sync public int phase = 1;
    @Sync public boolean enraged = false;
}

EriCap — API statique

EriCap est le point d'entree principal pour interagir avec les capabilities. Toutes ses methodes sont statiques, tu peux les appeler depuis n'importe ou sans creer d'instance.

EriCap
fr.eri.eriapi.capability.EriCap
API statique

Enregistrement, lecture et synchronisation des capabilities en une ligne.

  • static void register(Class<?> capClass) Enregistre une classe annotee @EriCapability aupres de Forge. Appeler en preInit.
  • static <T> T get(Entity entity, Class<T> capClass) Recupere l'instance de la capability attachee a une entite (joueur ou mob). Retourne null si la capability n'est pas disponible.
  • static <T> T get(ItemStack stack, Class<T> capClass) Recupere l'instance de la capability attachee a un ItemStack.
  • static <T> T get(TileEntity tile, Class<T> capClass) Recupere l'instance de la capability attachee a un TileEntity.
  • static void sync(EntityPlayerMP player, Class<?> capClass) Force la synchronisation des champs @Sync de la capability vers le client du joueur. A appeler apres avoir modifie des donnees cote serveur.

Enregistrement en preInit

L'enregistrement doit se faire pendant la phase preInit de ton mod, avant que Forge n'ait initialise les entites et items. Un appel par classe annotee suffit.

Java — Enregistrement dans la classe principale du mod
import fr.eri.eriapi.capability.EriCap;

@Mod(modid = "mymod", version = "1.0")
public class MyMod {

    @Mod.EventHandler
    public void preInit(FMLPreInitializationEvent event) {
        // Enregistre toutes tes capabilities ici
        EriCap.register(Mana.class);
        EriCap.register(PlayerStats.class);
        EriCap.register(SoulBound.class);
    }
}

Lire et modifier une capability

Java — Lecture sur un joueur
// Depuis un event handler ou une commande cote serveur :
Mana mana = EriCap.get(player, Mana.class);

if (mana != null) {
    // Lire
    int manaActuel = mana.current;
    int manaMax = mana.max;

    // Modifier
    mana.current = Math.max(0, mana.current - 10);

    // Synchroniser avec le client
    EriCap.sync((EntityPlayerMP) player, Mana.class);
}
Java — Lecture sur un ItemStack
ItemStack epee = player.getHeldItemMainhand();
SoulBound data = EriCap.get(epee, SoulBound.class);

if (data != null && !data.bound) {
    data.bound = true;
    data.ownerName = player.getName();
}
Java — Lecture sur un TileEntity
TileEntity tile = world.getTileEntity(pos);
MachineData machineData = EriCap.get(tile, MachineData.class);

if (machineData != null) {
    machineData.energy += 100;
    machineData.active = machineData.energy > 0;
}
Toujours verifier null

EriCap.get() peut retourner null si la capability n'est pas disponible sur cet objet (par exemple si tu appelles get(player, SoulBound.class) alors que SoulBound a target = ITEM). Verifie toujours le retour avant de l'utiliser.

@Sync — Synchronisation automatique

L'annotation @Sync se place sur les champs d'une capability pour indiquer qu'ils doivent etre envoyes au client. Les champs sans @Sync restent uniquement cote serveur — c'est utile pour des donnees internes que le client n'a pas besoin de connaitre.

@Sync
fr.eri.eriapi.capability.Sync
Annotation de champ

Marque un champ pour la synchronisation automatique serveur → client.

Quand la synchronisation se declenche-t-elle ?

EriAPI synchronise automatiquement les champs @Sync dans trois situations, sans que tu aies besoin d'ecrire quoi que ce soit :

  • Login — Quand le joueur se connecte au serveur, toutes ses capabilities sont envoyees.
  • Respawn — Apres la mort et le respawn, les donnees sont re-synchronisees.
  • Changement de dimension — Lors d'un voyage au Nether ou End, les donnees sont re-envoyees.

Pour toute modification que tu fais manuellement en jeu (consommer du mana, changer de niveau, etc.), tu dois appeler EriCap.sync() explicitement apres la modification.

Java — Exemple de champs synces vs non synces
@EriCapability(modId = "myrpg", target = AttachTarget.PLAYER)
public class PlayerStats {
    // Synce : le client a besoin de ces valeurs pour l'affichage HUD
    @Sync public int level = 1;
    @Sync public int experience = 0;
    @Sync public int health = 100;

    // Non synce : calcul interne, le client n'en a pas besoin
    public long lastDamageTimestamp = 0L;
    public float regenAccumulator = 0f;
}
Types de champs supportes par @Sync

Les types primitifs Java (int, float, double, boolean, long, byte, short), String, et les tableaux de ces types sont supportes. Les objets complexes doivent etre serialis es manuellement.

Cycle de vie d'une Capability

Voici dans quel ordre les evenements se produisent pour une capability attachee a un joueur, depuis l'enregistrement jusqu'a la mort et le respawn.

Sequence des evenements
  • 1. preInit — EriCap.register() Tu enregistres ta classe. EriAPI genere l'interface, l'impl, le storage et le provider Forge en interne.
  • 2. Login — sync automatique Quand le joueur rejoint le serveur, EriAPI envoie automatiquement tous les champs @Sync au client.
  • 3. En jeu — EriCap.sync() manuel Apres chaque modification de donnees que le client doit connaitre, appelle EriCap.sync().
  • 4. Mort — copie selon persistOnDeath Si persistOnDeath = true, toutes les donnees sont copiees sur la nouvelle instance du joueur. Sinon, les valeurs par defaut sont utilisees.
  • 5. Respawn / Dimension — re-sync automatique Apres respawn ou changement de dimension, EriAPI re-synchronise automatiquement tous les champs @Sync avec le client.
Java — Schema du cycle de vie complet
// === PHASE preInit ===
EriCap.register(Mana.class); // Une seule fois au demarrage du mod

// === PHASE en jeu (cote serveur) ===
// Le joueur lance un sort dans un event handler :
@SubscribeEvent
public void onPlayerInteract(PlayerInteractEvent.RightClickItem event) {
    EntityPlayer player = event.getEntityPlayer();
    if (player.world.isRemote) return; // On est cote serveur uniquement

    Mana mana = EriCap.get(player, Mana.class);
    if (mana != null && mana.current >= 20) {
        mana.current -= 20;
        // Forcer la mise a jour cote client
        EriCap.sync((EntityPlayerMP) player, Mana.class);
    }
}

// === MORT ===
// Si persistOnDeath = true, EriAPI copie mana.current et mana.max
// automatiquement lors du PlayerEvent.Clone de Forge.

// === RESPAWN ===
// EriAPI re-envoie les champs @Sync au client automatiquement.

Exemple complet — Systeme de Mana

Voici un exemple complet et fonctionnel d'un systeme de mana pour un mod RPG. Il montre la declaration de la capability, l'enregistrement, la consommation de mana dans une commande, et la regeneration passive via un event tick.

Etape 1 — Declarer la capability

Java — Mana.java
package fr.tonnom.myrpg.capability;

import fr.eri.eriapi.capability.EriCapability;
import fr.eri.eriapi.capability.AttachTarget;
import fr.eri.eriapi.capability.Sync;

@EriCapability(modId = "myrpg", target = AttachTarget.PLAYER)
public class Mana {
    @Sync public int current = 100;
    @Sync public int max = 100;
    public float regenRate = 0.5f; // non synce, serveur uniquement
}

Etape 2 — Enregistrer en preInit

Java — MyRpgMod.java
package fr.tonnom.myrpg;

import fr.eri.eriapi.capability.EriCap;
import fr.tonnom.myrpg.capability.Mana;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.common.event.FMLPreInitializationEvent;

@Mod(modid = "myrpg", version = "1.0")
public class MyRpgMod {

    @Mod.EventHandler
    public void preInit(FMLPreInitializationEvent event) {
        EriCap.register(Mana.class);
    }
}

Etape 3 — Consommer du mana dans un sort

Java — Consommation de mana lors d'un sort
import fr.eri.eriapi.capability.EriCap;
import fr.tonnom.myrpg.capability.Mana;
import net.minecraft.entity.player.EntityPlayerMP;
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
import net.minecraftforge.event.entity.player.PlayerInteractEvent;

public class SpellHandler {

    @SubscribeEvent
    public void onRightClick(PlayerInteractEvent.RightClickItem event) {
        if (event.getEntityPlayer().world.isRemote) return;

        EntityPlayerMP player = (EntityPlayerMP) event.getEntityPlayer();
        Mana mana = EriCap.get(player, Mana.class);

        if (mana == null) return;

        int coutMana = 20;

        if (mana.current >= coutMana) {
            mana.current -= coutMana;
            EriCap.sync(player, Mana.class); // Mettre a jour le client
            // Lancer le sort...
            player.sendMessage(new TextComponentString(
                "Sort lance ! Mana restant : " + mana.current + "/" + mana.max
            ));
        } else {
            player.sendMessage(new TextComponentString(
                "Pas assez de mana ! (" + mana.current + "/" + coutMana + " requis)"
            ));
        }
    }
}

Etape 4 — Regeneration passive au tick

Java — Regeneration de mana toutes les secondes
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
import net.minecraftforge.fml.common.gameevent.TickEvent;

public class ManaRegenHandler {

    @SubscribeEvent
    public void onPlayerTick(TickEvent.PlayerTickEvent event) {
        if (event.phase != TickEvent.Phase.END) return;
        if (event.player.world.isRemote) return;
        if (event.player.ticksExisted % 20 != 0) return; // 1 fois par seconde

        EntityPlayerMP player = (EntityPlayerMP) event.player;
        Mana mana = EriCap.get(player, Mana.class);

        if (mana != null && mana.current < mana.max) {
            mana.current = Math.min(mana.max, mana.current + (int) mana.regenRate);
            EriCap.sync(player, Mana.class);
        }
    }
}
Lecture cote client

Sur le client (pour afficher la valeur dans un HUD par exemple), utilise exactement le meme EriCap.get(player, Mana.class). Les champs @Sync seront a jour grace a la synchronisation automatique.

Resultat

En quelques dizaines de lignes de code — sans aucun boilerplate Forge — tu as un systeme de mana complet :

  • Donnees persistees en NBT automatiquement
  • Copiees a la mort du joueur (persistOnDeath = true par defaut)
  • Synchronisees cote client a la connexion, au respawn et manuellement
  • Lisibles depuis n'importe quel contexte (commande, event, GUI, HUD)