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.

Package : fr.eri.eriapi.data NBT automatique Java 8

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.

Qu'est-ce qu'un POJO ?

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 @Sync du 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.

Java — Initialisation dans preInit
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);
}
Ordre d'appel important

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.

Java — Exemple d'une classe annotee @EriData
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.
Toujours initialiser les champs

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.

Java — Utilisation de @Sync
@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 = "";
}
Tous les champs sont sauvegardes

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.
Java — Les trois scopes
// 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)

Java — Lire et modifier les donnees d'un joueur
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)

Java — Lire et modifier les donnees d'un monde
// 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.
Toujours appeler save() apres une modification

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<>();
Types non supportes

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.
Sync manuel possible

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

Java — RpgStats.java
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

Java — Classe principale du mod
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

Java — Ajouter de l'XP et gerer le passage de niveau
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

Java — Lire les donnees @Sync cote client dans une GUI EriAPI
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));
    }
}
Et voila !

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.