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.
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.
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.
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, ouTILE_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.
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
}
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.
@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.
-
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.
// 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.
Enregistrement, lecture et synchronisation des capabilities en une ligne.
-
static void register(Class<?> capClass)
Enregistre une classe annotee
@EriCapabilityaupres de Forge. Appeler enpreInit. -
static <T> T get(Entity entity, Class<T> capClass)
Recupere l'instance de la capability attachee a une entite (joueur ou mob). Retourne
nullsi 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
@Syncde 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.
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
// 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);
}
ItemStack epee = player.getHeldItemMainhand();
SoulBound data = EriCap.get(epee, SoulBound.class);
if (data != null && !data.bound) {
data.bound = true;
data.ownerName = player.getName();
}
TileEntity tile = world.getTileEntity(pos);
MachineData machineData = EriCap.get(tile, MachineData.class);
if (machineData != null) {
machineData.energy += 100;
machineData.active = machineData.energy > 0;
}
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.
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.
@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;
}
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.
- 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
@Syncau 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
@Syncavec le client.
// === 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
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
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
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
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);
}
}
}
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)