Data & Storage
Persiste les donnees de tes joueurs et de ton monde avec une simple annotation sur un POJO. Sauvegarde NBT automatique, synchronisation serveur-client, multi-scope.
Presentation
Le module Data & Storage resout un probleme tres courant dans le developpement de mods : sauvegarder et synchroniser des donnees liees aux joueurs ou au monde.
Normalement, faire cela dans Minecraft Forge demande d'implementer des interfaces complexes
(IExtendedEntityProperties, WorldSavedData), d'ecrire soi-meme la serialisation NBT,
et de gerer manuellement les packets de synchronisation. C'est fastidieux et source d'erreurs.
Avec EriAPI, tu annotes simplement un POJO (une classe Java ordinaire) avec @EriData,
et le framework s'occupe de tout : lecture, ecriture NBT, envoi au client, et persistance apres la mort.
Un POJO (Plain Old Java Object) est une classe Java toute simple, sans heritage special. Juste des champs et eventuellement des methodes. C'est exactement ce qu'EriAPI attend — tu n'as pas a implementer d'interface particuliere.
Ce que le module fait pour toi
- Serialise et deserialise automatiquement tous les champs vers/depuis le format NBT de Minecraft
- Attache les donnees au bon scope : joueur, monde, ou global
- Synchronise les champs marques
@Syncdu serveur vers le client automatiquement - Gere la persistance apres la mort via
PlayerEvent.Clone - Re-synchronise au login, respawn, et changement de dimension
Setup
L'initialisation du module se fait dans le preInit de ta classe principale de mod.
Tu enregistres chaque classe de donnees que tu veux gerer.
import fr.eri.eriapi.data.DataManager;
@Mod.EventHandler
public void preInit(FMLPreInitializationEvent event) {
// Initialise le module Data & Storage
DataManager.init();
// Enregistre tes classes de donnees
DataManager.register(PlayerStats.class);
DataManager.register(WorldQuests.class);
}
DataManager.init() doit etre appele avant tout register().
Fais-le dans preInit, pas dans init ou postInit.
@EriData — Annotation de classe
@EriData est l'annotation principale. Tu la places sur la classe qui contient tes donnees.
Elle indique au framework comment gerer cette classe : pour quel mod, dans quel scope, et si les donnees
doivent survivre a la mort du joueur.
import fr.eri.eriapi.data.EriData;
import fr.eri.eriapi.data.DataScope;
import fr.eri.eriapi.data.Sync;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@EriData(modId = "mymod", scope = DataScope.PLAYER, persistOnDeath = true)
public class PlayerStats {
// Champs @Sync : sauvegardes ET envoyes au client
@Sync public int level = 1;
@Sync public float xp = 0f;
@Sync public int coins = 0;
// Champs non-@Sync : sauvegardes uniquement cote serveur
public String lastLogin = "";
public List<String> achievements = new ArrayList<>();
public Map<String, String> settings = new HashMap<>();
}
Parametres de @EriData
| Parametre | Type | Requis | Defaut | Description |
|---|---|---|---|---|
modId |
String | Oui | — | L'identifiant de ton mod (ex. "mymod"). Utilise pour nommer la cle NBT et eviter les conflits entre mods. |
scope |
DataScope | Non | PLAYER |
Portee des donnees : PLAYER, WORLD, ou GLOBAL. Voir la section DataScope. |
persistOnDeath |
boolean | Non | true |
Si true, les donnees sont copiees lors de la mort du joueur via PlayerEvent.Clone. Si false, les donnees sont remises a zero a chaque mort. |
filename |
String | Non | "" |
Nom du fichier de sauvegarde pour les scopes WORLD et GLOBAL. Si vide, les donnees sont stockees dans le fichier partage eriapi_data.dat. Si specifie, un fichier dedie est cree : world/data/{filename}.dat. Recommande pour isoler les donnees de ton mod. |
Donne toujours une valeur par defaut a tes champs (= 1, = "", = new ArrayList<>()).
Si un champ n'a pas de valeur par defaut et que la cle NBT n'existe pas encore (premiere connexion du joueur),
tu risques une NullPointerException.
@Sync — Annotation de champ
@Sync marque un champ pour la synchronisation serveur vers client.
Dans Minecraft, le code s'execute en deux endroits distincts : le serveur (qui gere la logique du jeu) et le client (qui gere l'affichage). Par defaut, les donnees sauvegardees en NBT ne sont visibles que cote serveur. Si tu veux afficher une valeur dans une GUI cote client (par exemple le niveau du joueur), tu dois la synchroniser.
@EriData(modId = "mymod", scope = DataScope.PLAYER)
public class PlayerStats {
// Ce champ est envoye au client : tu peux l'afficher dans une GUI
@Sync public int level = 1;
// Ce champ reste cote serveur uniquement : pas de @Sync
public String serverSideSecret = "";
}
L'absence de @Sync n'empeche pas la sauvegarde. Tous les champs de la classe sont
serialises en NBT, que tu les annotes ou non. @Sync controle uniquement l'envoi au client
par packet.
DataScope — Enum
DataScope definit la portee de tes donnees : a quoi sont-elles attachees ?
| Valeur | Portee | Cas d'usage |
|---|---|---|
PLAYER |
Par joueur | Statistiques de joueur, inventaire custom, progression RPG, preferences. Chaque joueur a son propre exemplaire des donnees. |
WORLD |
Par monde (sauvegarde) | Quetes actives dans ce monde, etat de la carte, donnees de factions. Les donnees changent selon le monde charge. |
GLOBAL |
Partage entre tous les mondes | Configuration globale du serveur, tables de loot partagees, donnees d'un service externe. Une seule instance, accessible partout. |
// Donnees par joueur : chaque joueur a ses propres stats
@EriData(modId = "myrpg", scope = DataScope.PLAYER)
public class PlayerStats { ... }
// Donnees par monde : l'etat des quetes depend du monde
@EriData(modId = "myrpg", scope = DataScope.WORLD)
public class WorldQuests { ... }
// Donnees monde avec fichier dedie (recommande)
// Cree world/data/myrpg_factions.dat au lieu du fichier partage
@EriData(modId = "myrpg", scope = DataScope.WORLD, filename = "myrpg_factions")
public class FactionData { ... }
// Donnees globales : partagees entre tous les mondes du serveur
@EriData(modId = "myrpg", scope = DataScope.GLOBAL)
public class GlobalConfig { ... }
DataManager — API statique
DataManager est le point d'entree principal pour lire et ecrire tes donnees.
Toutes ses methodes sont statiques — pas besoin d'instancier quoi que ce soit.
Donnees joueur (scope PLAYER)
import fr.eri.eriapi.data.DataManager;
// Recuperer les donnees du joueur (cree l'instance si elle n'existe pas encore)
PlayerStats stats = DataManager.get(player, PlayerStats.class);
// Lire une valeur
int niveau = stats.level;
// Modifier une valeur
stats.level++;
stats.coins += 50;
// Sauvegarder les modifications en NBT et synchroniser les champs @Sync
DataManager.save(player, PlayerStats.class);
Donnees monde (scope WORLD)
// Recuperer les donnees du monde
WorldQuests quests = DataManager.getWorld(world, WorldQuests.class);
// Modifier
quests.activeQuests.add("quest_dragon_1");
// Sauvegarder
DataManager.saveWorld(world, WorldQuests.class);
Reference des methodes
| Methode | Description |
|---|---|
init() |
Initialise le module et enregistre les event listeners Forge. A appeler en premier dans preInit. |
register(Class) |
Enregistre une classe annotee @EriData. A appeler apres init(). |
get(player, Class) |
Retourne l'instance des donnees pour ce joueur. Cree et initialise l'instance si c'est la premiere fois. |
save(player, Class) |
Serialise les donnees en NBT et envoie un packet de sync pour les champs @Sync. |
getWorld(world, Class) |
Retourne l'instance des donnees pour ce monde. Cree et initialise l'instance si necessaire. |
saveWorld(world, Class) |
Serialise les donnees monde en NBT. |
Modifier les champs d'une instance n'ecrit pas automatiquement en NBT. Tu dois explicitement
appeler DataManager.save() ou DataManager.saveWorld() apres chaque modification
que tu veux persister.
DataSerializer — Types supportes
Le DataSerializer est le composant interne qui convertit tes champs Java en tags NBT
et inversement. Il supporte les types suivants :
| Type Java | Tag NBT | Exemple |
|---|---|---|
int |
NBTTagInt | public int level = 1; |
float |
NBTTagFloat | public float xp = 0f; |
double |
NBTTagDouble | public double distance = 0.0; |
boolean |
NBTTagByte (0/1) | public boolean unlocked = false; |
long |
NBTTagLong | public long playtime = 0L; |
short |
NBTTagShort | public short rank = 0; |
byte |
NBTTagByte | public byte flags = 0; |
String |
NBTTagString | public String name = ""; |
Enum |
NBTTagString (nom) | public MyEnum mode = MyEnum.DEFAULT; |
List<String> |
NBTTagList | public List<String> quests = new ArrayList<>(); |
Map<String, String> |
NBTTagCompound | public Map<String, String> prefs = new HashMap<>(); |
Les types complexes comme des classes custom imbriquees, List<Integer>, ou
Map<String, Integer> ne sont pas serialises automatiquement. Utilise
List<String> et convertis manuellement si besoin, ou decompose en plusieurs champs.
Synchronisation automatique
EriAPI ecoute plusieurs evenements Forge pour s'assurer que les champs @Sync arrivent
toujours a jour cote client.
| Evenement | Action |
|---|---|
Connexion du joueur (PlayerLoggedInEvent) |
Charge les donnees NBT et envoie tous les champs @Sync au client. |
Respawn apres mort (PlayerRespawnEvent) |
Re-envoie les champs @Sync au client apres chargement de l'ecran de respawn. |
Changement de dimension (PlayerChangedDimensionEvent) |
Re-synchronise les donnees car le client recharge l'entite joueur lors d'un voyage inter-dimension. |
Mort du joueur (PlayerEvent.Clone) |
Si persistOnDeath = true, copie les donnees de l'ancienne entite vers la nouvelle. Si false, les donnees repartent aux valeurs par defaut. |
Si tu modifies des donnees et que tu veux les envoyer immediatement au client sans attendre l'un
des evenements ci-dessus, appelle simplement DataManager.save(player, MaClasse.class).
Cela ecrit en NBT et envoie le packet de sync en meme temps.
Exemple complet — RPG Stats
Voici un exemple concret et complet : un systeme de stats RPG avec gain d'experience et passage de niveau.
Etape 1 — Definir la classe de donnees
package fr.tonnom.myrpg.data;
import fr.eri.eriapi.data.EriData;
import fr.eri.eriapi.data.DataScope;
import fr.eri.eriapi.data.Sync;
import java.util.ArrayList;
import java.util.List;
@EriData(modId = "myrpg", scope = DataScope.PLAYER)
public class RpgStats {
@Sync public int level = 1;
@Sync public float xp = 0;
@Sync public int health = 20;
@Sync public int mana = 100;
public List<String> completedQuests = new ArrayList<>();
}
Etape 2 — Enregistrer dans preInit
import fr.eri.eriapi.data.DataManager;
import fr.tonnom.myrpg.data.RpgStats;
@Mod.EventHandler
public void preInit(FMLPreInitializationEvent event) {
DataManager.init();
DataManager.register(RpgStats.class);
}
Etape 3 — Utiliser les donnees en jeu
import fr.eri.eriapi.data.DataManager;
import fr.tonnom.myrpg.data.RpgStats;
// Recuperer les stats du joueur
RpgStats stats = DataManager.get(player, RpgStats.class);
// Ajouter de l'XP
stats.xp += 50;
// Verifier si le joueur passe de niveau
if (stats.xp >= stats.level * 100) {
stats.level++;
stats.xp = 0;
stats.mana += 10; // Bonus de mana au niveau up
player.sendMessage(new TextComponentString(
"Niveau " + stats.level + " atteint !"
));
}
// Sauvegarder et synchroniser au client
DataManager.save(player, RpgStats.class);
Etape 4 — Afficher les stats dans une GUI
import fr.eri.eriapi.data.DataManager;
import fr.eri.eriapi.gui.EriGuiScreen;
import fr.eri.eriapi.gui.components.Label;
import fr.eri.eriapi.gui.components.ProgressBar;
import fr.eri.eriapi.gui.util.Colors;
import fr.tonnom.myrpg.data.RpgStats;
public class StatsScreen extends EriGuiScreen {
public StatsScreen() {
super(1920, 1080);
}
@Override
public void initGui() {
super.initGui();
// Recuperer les stats (disponibles cote client grace aux @Sync)
RpgStats stats = DataManager.get(
net.minecraft.client.Minecraft.getMinecraft().player,
RpgStats.class
);
// Afficher le niveau
addComponent(new Label(760, 300, 400, 30)
.text("Niveau " + stats.level)
.color(Colors.CYAN)
.align(Label.Align.CENTER)
.scale(1.4f));
// Barre d'XP
float xpPercent = stats.xp / (stats.level * 100f);
addComponent(new ProgressBar(660, 360, 600, 20)
.value(xpPercent)
.color(Colors.PURPLE)
.cornerRadius(4)
.showPercent(true));
}
}
En quelques classes et annotations, tu as un systeme de persistance complet : les stats sont sauvegardees en NBT, survivent aux redemarrages du serveur, sont conservees apres la mort du joueur, et sont disponibles en temps reel dans ta GUI cote client.