Overlay HUD

Affiche des informations en permanence sur le HUD du joueur — barres de vie, stats de faction, effets visuels — sans bloquer le jeu.

fr.eri.eriapi.overlay Client-side 1920x1080 design

Qu'est-ce qu'un overlay ?

Un overlay est un element graphique affiche en permanence sur l'ecran du joueur, par-dessus le jeu et le HUD vanilla de Minecraft. Contrairement a un GUI (EriGuiScreen) qui prend le controle de l'ecran, un overlay reste affiché en permanence pendant que le joueur joue.

Cas d'usage typiques :

  • Barres de ressources — mana, endurance, energie, chaleur
  • HUD de faction — nom de faction, grade, points de territoire
  • Indicateurs de statut — effets actifs, buffs/debuffs
  • Minimap ou boussole personnalisee
  • Effets decoratifs — particules, aurora, fumee ambiante

Overlay vs GUI — quelle difference ?

Caracteristique EriGuiScreen (GUI) EriOverlay (HUD)
Ouverture Sur action du joueur (touche, commande) Permanent (enregistre une fois)
Bloque le jeu Oui — le joueur ne peut pas bouger Non — le joueur joue normalement
Interactions Clavier, souris, clics Aucune (rendu uniquement)
Coordonnees 1920x1080 design pixels 1920x1080 design pixels (identique)
Composants Tous les composants EriAPI Composants visuels seulement (pas d'input)
Cycle de vie Ouvert / ferme Register / unregister + show / hide
Meme systeme de composants

Les overlays utilisent exactement les memes composants, le meme systeme de coordonnees 1920x1080 et les memes animations qu'un GUI classique. Si tu sais creer un EriGuiScreen, tu sais deja creer un overlay.

EriOverlay

EriOverlay est la classe de base abstraite pour tous les overlays HUD. Tu crees une sous-classe, tu definis les composants dans buildOverlay(), et tu l'enregistres dans OverlayManager.

CLASS ABSTRACT
EriOverlay
fr.eri.eriapi.overlay.EriOverlay

Creation d'un overlay

Java — Overlay minimal
import fr.eri.eriapi.overlay.*;
import fr.eri.eriapi.gui.components.*;

public class HealthOverlay extends EriOverlay {

    public HealthOverlay() {
        // id unique, largeur et hauteur en design pixels
        super("health_hud", 200, 60);

        setAnchor(Anchor.BOTTOM_LEFT);   // coin inferieur gauche
        setOffset(10, -10);              // decalage depuis l'ancrage
    }

    @Override
    protected void buildOverlay() {
        getRoot().add(
            new Rectangle(0, 0, 200, 60)
                .fillColor(0x80000000)
                .cornerRadius(8),
            new Label(10, 20, 180, 20, "HP : 20")
                .color(0xFFFF5555)
        );
    }
}

// Enregistrement (une fois, dans ClientProxy.init() par exemple) :
OverlayManager.getInstance().register(new HealthOverlay());

Cycle de vie

La methode buildOverlay() est appelee une seule fois au premier rendu (lazy initialization). Les appels suivants a render() reutilisent les composants deja construits. Si tu veux forcer une reconstruction (donnees qui changent), appelle rebuild().

EriOverlay — methodes
fr.eri.eriapi.overlay.EriOverlay
API publique
  • EriOverlay(String id, int designWidth, int designHeight)
    Constructeur. id est l'identifiant unique utilise par OverlayManager. designWidth et designHeight definissent la zone de l'overlay en pixels de design (1920x1080).
    Constructeur
  • void buildOverlay()
    Methode abstraite a implementer. Appelle getRoot().add(...) pour ajouter des composants. Appelee une seule fois au premier rendu.
    A implementer
  • ContainerComponent getRoot()
    Retourne le conteneur racine. A utiliser depuis buildOverlay() pour ajouter des composants.
    Getter
  • void rebuild()
    Force la reconstruction de l'overlay au prochain rendu. Utile pour rafraichir l'affichage apres un changement de donnees important.
    Methode
  • EriOverlay setAnchor(Anchor anchor)
    Definit le point d'ancrage de l'overlay sur l'ecran. Voir la section Anchor. Defaut : TOP_LEFT.
    Fluent
  • EriOverlay setOffset(int offsetX, int offsetY)
    Decalage en pixels de design depuis l'ancrage. Positif = bas/droite, negatif = haut/gauche.
    Fluent
  • EriOverlay setLayer(OverlayLayer layer)
    Definit le layer de rendu (moment dans le pipeline HUD). Defaut : POST_ALL.
    Fluent
  • EriOverlay hideInGui(boolean hide)
    Si true (defaut), l'overlay disparait quand un GUI est ouvert. Mettre a false pour toujours afficher l'overlay meme dans les menus.
    Fluent
  • EriOverlay hideOnF1(boolean hide)
    Si true (defaut), l'overlay disparait quand le joueur appuie sur F1 (masquer le HUD). Mettre a false pour ignorer F1.
    Fluent
  • void show() / hide() / toggle()
    Controle direct de la visibilite de cet overlay. Equivalent a OverlayManager.getInstance().show("id").
    Methode

OverlayManager

OverlayManager est le singleton qui centralise tous les overlays enregistres. Il gere automatiquement le rendu et les ticks via les events Forge — tu n'as rien a configurer au-dela de l'enregistrement initial.

SINGLETON
OverlayManager
fr.eri.eriapi.overlay.OverlayManager
Java — Enregistrement et controle
import fr.eri.eriapi.overlay.OverlayManager;

OverlayManager om = OverlayManager.getInstance();

// Enregistrer des overlays (une fois, en init)
om.register(new HealthOverlay());
om.register(new ManaOverlay());
om.register(new FactionHudOverlay());

// Controle de la visibilite par ID
om.show("health_hud");
om.hide("health_hud");
om.toggle("health_hud");

// Controle global
om.hideAll();
om.showAll();

// Recuperer un overlay pour le modifier
EriOverlay overlay = om.get("health_hud");
if (overlay != null) {
    overlay.rebuild(); // Forcer une reconstruction
}

// Desenregistrer un overlay
om.unregister("health_hud");
OverlayManager — methodes
fr.eri.eriapi.overlay.OverlayManager
Singleton
  • static OverlayManager getInstance()
    Retourne l'instance unique du gestionnaire.
    Statique
  • void register(EriOverlay overlay)
    Enregistre un overlay. Si un overlay avec le meme ID existe deja, il est remplace. L'ordre d'enregistrement determine l'ordre de rendu.
    Methode
  • void unregister(String id)
    Supprime un overlay enregistre. Sans effet si l'ID est inconnu.
    Methode
  • EriOverlay get(String id)
    Retourne l'overlay correspondant a l'ID, ou null s'il n'existe pas.
    Getter
  • void show(String id) / hide(String id) / toggle(String id)
    Controle la visibilite d'un overlay par son ID. Sans effet si l'ID est inconnu.
    Methode
  • void showAll() / hideAll()
    Affiche ou masque tous les overlays enregistres.
    Methode
Ou enregistrer les overlays ?

Enregistre tes overlays dans ClientProxy.init() (ou postInit() si tu as besoin que les items/blocs soient charges en premier). Ne les enregistre pas dans un constructeur de GUI ou dans un event qui se declenche souvent — le register est fait une seule fois.

Anchor

L'enum Anchor definit les neuf points d'ancrage possibles pour un overlay. L'ancrage est le coin ou le bord de l'ecran depuis lequel l'overlay est positionne. Combine avec setOffset(x, y) pour affiner le positionnement.

ENUM
Anchor
fr.eri.eriapi.overlay.Anchor
Valeur Position sur l'ecran Cas d'usage typique
TOP_LEFT Coin superieur gauche (0, 0) Minimap, logo de mod
TOP_CENTER Centre du bord superieur Titre de zone, boss bar
TOP_RIGHT Coin superieur droit Horloge, coordonnees, ping
CENTER_LEFT Centre du bord gauche Barre de statut laterale
CENTER Centre absolu de l'ecran Reticule custom, alerte centrale
CENTER_RIGHT Centre du bord droit Liste de buffs/debuffs
BOTTOM_LEFT Coin inferieur gauche Barre de mana/endurance
BOTTOM_CENTER Centre du bord inferieur Barre d'action, raccourcis
BOTTOM_RIGHT Coin inferieur droit Stats de faction, nom de faction
Java — Utilisation de l'ancrage avec offset
// Overlay en bas a droite avec 15px de marge
new MonOverlay()
    .setAnchor(Anchor.BOTTOM_RIGHT)
    .setOffset(-15, -15);  // negatif = vers le haut/gauche

// Overlay centre en haut avec 20px de marge du bord
new BossBarOverlay()
    .setAnchor(Anchor.TOP_CENTER)
    .setOffset(0, 20);     // positif = vers le bas
Coordonnees de design

Les offsets sont en pixels de design (espace 1920x1080), comme pour les composants GUI. EriAPI les convertit automatiquement vers la resolution reelle du joueur. Un offset de (-15, -15) representera la meme marge visuelle sur un ecran 1080p ou 4K.

OverlayLayer

OverlayLayer definit a quel moment dans le pipeline de rendu HUD de Minecraft ton overlay est dessine. Cela determine ce qui est affiche par-dessus ou par-dessous.

ENUM
OverlayLayer
fr.eri.eriapi.overlay.OverlayLayer
Valeur Moment du rendu Consequence
PRE_ALL Avant tous les elements HUD vanilla Ton overlay est affiche sous la barre de vie, l'inventaire rapide, etc.
POST_HOTBAR Apres la barre d'items (hotbar) S'affiche par-dessus la hotbar mais sous la barre de vie
POST_EXPERIENCE Apres la barre d'experience S'affiche apres l'XP bar, avant les autres elements
POST_ALL Apres tous les elements HUD vanilla (defaut) Ton overlay est affiche par-dessus tout le HUD vanilla
Java — Changer le layer de rendu
// Afficher l'overlay par-dessus tout le HUD (comportement par defaut)
overlay.setLayer(OverlayLayer.POST_ALL);

// Afficher juste apres la hotbar
overlay.setLayer(OverlayLayer.POST_HOTBAR);

// Fond ambiant sous tout le HUD
overlay.setLayer(OverlayLayer.PRE_ALL);

Composants utilisables dans les overlays

Les overlays reutilisent exactement les memes composants que les GUIs. La seule difference : les composants interactifs (TextField, ScrollList, TabView, etc.) n'ont aucune utilite dans un overlay car il n'y a pas d'interaction clavier/souris avec le HUD.

Formes et fonds

Rectangle
fr.eri.eriapi.gui.components.Rectangle
Forme

Rectangle uni avec option coins arrondis et bordure. Parfait pour les fonds de panneau HUD.

Java
new Rectangle(0, 0, 200, 60)
    .fillColor(0x80000000)   // fond semi-transparent
    .borderColor(0x40FFFFFF) // bordure subtile
    .cornerRadius(8);
GradientRectangle
fr.eri.eriapi.gui.components.GradientRectangle
Forme

Rectangle avec degrade lineaire (vertical ou horizontal). Ideal pour les barres d'ambiance ou les fonds degrades.

Java
// Degrade vertical violet → bleu
new GradientRectangle()
    .originalPos(0, 0).originalSize(200, 60)
    .vertical(0xFF6B2FA0, 0xFF00E5FF)
    .cornerRadius(8);

// Degrade horizontal
new GradientRectangle()
    .originalPos(0, 0).originalSize(300, 8)
    .horizontal(0xFF00E5FF, 0x00000000); // fondu vers transparent
Circle / Triangle
fr.eri.eriapi.gui.components
Forme

Cercle plein ou triangle (indicateur directionnel). Utiles pour des icones ou fleches de navigation.

Texte

Label
fr.eri.eriapi.gui.components.Label
Texte

Affichage de texte avec scale, alignement et ombre. Le composant texte de base pour tous les HUD.

Java
new Label(10, 10, 180, 20, "Faction : Erinien")
    .color(0xFFFFD700)
    .scale(1.2f)
    .shadow(true);

Controles de progression

ProgressBar
fr.eri.eriapi.gui.components.ProgressBar
Controle

Barre de progression avec fill anime, couleurs et coins arrondis. Le composant ideal pour afficher des ressources (mana, vie, exp).

Java
ProgressBar manaBar = new ProgressBar(0, 0, 180, 14)
    .progress(0.75f)                // 75% de mana
    .fillColor(0xFF6B2FA0)          // couleur du remplissage
    .backgroundColor(0x40000000)   // fond
    .cornerRadius(7)
    .showText(false);               // pas de texte en overlay

Conteneurs

ContainerComponent
fr.eri.eriapi.gui.core.ContainerComponent
Layout

Conteneur pour imbriquer des composants. Permet de grouper des elements et d'appliquer une opacity ou des animations collectives. La methode getRoot() de EriOverlay retourne deja un ContainerComponent racine — utilise des sous-conteneurs pour organiser ton HUD en sections logiques.

Effets visuels decoratifs

Ces composants sont particulierement adaptes aux overlays : ils s'animent en continu, consomment peu de ressources et apportent une ambiance visuelle sans necessiter de configuration de l'AnimationManager.

Aurora
fr.eri.eriapi.gui.components.Aurora
Effet visuel

Bandes d'aurore boreale ondulantes avec degrade vertical. Jusqu'a 10 bandes superposables, chacune avec sa propre couleur, frequence, amplitude et vitesse. Anime en continu via System.currentTimeMillis() — pas besoin d'animation EriAPI.

Java — Aurora en overlay de fond
new Aurora()
    .originalPos(0, 0).originalSize(1920, 400)
    .addBand(0x406B2FA0, 0.3f, 1.2f, 80, 50)   // violet
    .addBand(0x3000E5FF, 0.5f, 0.8f, 60, 120)  // cyan
    .addBand(0x20FF6B6B, 0.7f, 0.6f, 50, 200)  // rose subtil
    .speed(0.02f);
// Parametres addBand : couleur, frequence, amplitude, hauteur, offsetY
Starfield
fr.eri.eriapi.gui.components.Starfield
Effet visuel

Champ d'etoiles avec scintillement sinusoidal et etoiles filantes aleatoires. Jusqu'a 3 etoiles filantes simultanees.

Java
new Starfield()
    .originalPos(0, 0).originalSize(600, 400)
    .starCount(80)
    .starColor(0xFFF0F2FF)
    .shootingStarChance(0.003f); // 0 = jamais, 0.01 = frequent
ParticleSystem
fr.eri.eriapi.gui.components.ParticleSystem
Effet visuel

Systeme de particules soft (cercles avec gradient radial) qui naissent, derivent et s'estompent. Blending additif pour un effet lumineux. Parfait pour une ambiance magique.

Java
new ParticleSystem()
    .originalPos(0, 0).originalSize(300, 100)
    .maxParticles(20)
    .spawnRate(0.3f)
    .particleLife(2000, 4000)
    .particleSize(20, 60)
    .particleColor(0x306B2FA0)
    .drift(0, -0.008f)     // monte doucement
    .spread(0.01f)
    .fadeIn(0.2f).fadeOut(0.4f);
SmokeFog
fr.eri.eriapi.gui.components.SmokeFog
Effet visuel

Brouillard organique base sur du bruit Simplex fractal (FBM). Anime en continu, le brouillard coule et se deforme naturellement. Tres subtil a faible intensite.

Java
new SmokeFog()
    .originalPos(0, 0).originalSize(400, 200)
    .color(0x6B2FA0)      // couleur RGB
    .intensity(0.08f)     // alpha max (tres subtil)
    .scale(0.006f)        // taille des volutes
    .speed(0.0003f)
    .octaves(3)
    .cellSize(12);

Animations et scope "overlay"

Toutes les animations EriAPI fonctionnent dans les overlays. Cependant, il y a une subtilite importante : quand un GUI est ferme, EriGuiScreen.onGuiClosed() appelle AnimationManager.getInstance().stopGuiAnimations() — cela arrete toutes les animations qui n'ont pas de scope.

Pour proteger les animations d'overlay contre cet arret, utilise .scope("overlay") :

Java — Animation d'overlay protegee avec .scope()
public class ManaOverlay extends EriOverlay {

    private Label manaLabel;
    private ProgressBar manaBar;

    public ManaOverlay() {
        super("mana_hud", 200, 40);
        setAnchor(Anchor.BOTTOM_LEFT);
        setOffset(10, -70); // au-dessus de la barre de vie vanilla
    }

    @Override
    protected void buildOverlay() {
        manaLabel = new Label(10, 5, 100, 15, "Mana")
            .color(0xFF9B59B6);

        manaBar = new ProgressBar(10, 22, 180, 10)
            .progress(1f)
            .fillColor(0xFF6B2FA0)
            .backgroundColor(0x40000000)
            .cornerRadius(5);

        getRoot().add(manaLabel, manaBar);

        // Animation respirante — scope "overlay" pour ne pas etre arretee
        // quand le joueur ouvre/ferme un GUI
        Animation anim = Animation.create(0.6f, 1f, 30)
            .pingPong()
            .scope("overlay")
            .easing(Easing.EASE_IN_OUT)
            .onUpdate(v -> manaLabel.setOpacity(v));
        AnimationManager.getInstance().play(anim);
    }

    // Methode publique pour mettre a jour la valeur de mana
    public void setMana(float ratio) {
        if (manaBar != null) {
            manaBar.progress(ratio);
        }
    }
}
Toujours utiliser .scope("overlay") pour les animations loopees

Sans ce scope, tes animations pingPong() ou loop() seront interrompues chaque fois que le joueur ferme un GUI (inventaire, etc.). Avec .scope("overlay"), elles continuent de tourner independamment.

Helpers d'animation sur les composants

Les methodes pré-faites de Component (fadeIn, breathe, pulse, etc.) fonctionnent aussi dans les overlays, mais elles ne definissent pas de scope automatiquement. Pour les animations en boucle dans un overlay, cree l'animation manuellement avec .scope("overlay").

Java — Comparaison : avec et sans scope
// ❌ Sera arrete quand le joueur ferme un GUI
label.breathe(40); // helper interne, pas de scope

// ✅ Continue meme quand un GUI est ouvert puis ferme
Animation.create(0.4f, 1f, 20)
    .pingPong()
    .scope("overlay")
    .easing(Easing.EASE_IN_OUT)
    .onUpdate(v -> label.setOpacity(v));
AnimationManager.getInstance().play(breatheAnim);

Exemple 1 — Barre de mana

Un overlay simple avec une barre de progression et un label, ancre en bas a gauche. La barre peut etre mise a jour depuis n'importe ou dans le code.

Java — ManaOverlay complet
import fr.eri.eriapi.overlay.*;
import fr.eri.eriapi.gui.components.*;

public class ManaOverlay extends EriOverlay {

    private ProgressBar manaBar;
    private Label manaLabel;

    public ManaOverlay() {
        super("mana_overlay", 210, 50);
        setAnchor(Anchor.BOTTOM_LEFT);
        setOffset(10, -80); // juste au-dessus de la barre de vie
        hideInGui(true);    // masquer si GUI ouvert
        hideOnF1(true);     // masquer si F1
    }

    @Override
    protected void buildOverlay() {
        // Fond
        getRoot().add(new Rectangle(0, 0, 210, 50)
            .fillColor(0x90000000)
            .cornerRadius(6));

        // Label "Mana"
        manaLabel = new Label(8, 6, 60, 12, "MANA")
            .color(0xFFBB86FC)
            .scale(0.8f)
            .shadow(true);

        // Barre de mana
        manaBar = new ProgressBar(8, 24, 194, 12)
            .progress(1f)
            .fillColor(0xFF6B2FA0)
            .backgroundColor(0x50000000)
            .cornerRadius(6);

        // Label valeur
        Label valueLabel = new Label(8, 38, 194, 10, "100 / 100")
            .color(0x80FFFFFF)
            .scale(0.75f)
            .align(Label.Align.CENTER);

        getRoot().add(manaLabel, manaBar, valueLabel);
    }

    /** Appele depuis ton systeme de mana pour mettre a jour la barre. */
    public void setMana(int current, int max) {
        if (manaBar != null) {
            manaBar.progress((float) current / max);
        }
    }
}

// Dans ClientProxy.init() :
ManaOverlay manaOverlay = new ManaOverlay();
OverlayManager.getInstance().register(manaOverlay);

// Depuis n'importe ou dans ton mod (cote client) :
ManaOverlay overlay = (ManaOverlay) OverlayManager.getInstance().get("mana_overlay");
if (overlay != null) overlay.setMana(75, 100);

Exemple 2 — HUD de faction

Un HUD de faction dans le coin inferieur droit avec le nom de la faction, le grade du joueur et le nombre de membres en ligne.

Java — FactionHudOverlay
import fr.eri.eriapi.overlay.*;
import fr.eri.eriapi.gui.components.*;

public class FactionHudOverlay extends EriOverlay {

    private Label factionNameLabel;
    private Label rankLabel;
    private Label membersLabel;

    public FactionHudOverlay() {
        super("faction_hud", 220, 70);
        setAnchor(Anchor.BOTTOM_RIGHT);
        setOffset(-10, -10);
    }

    @Override
    protected void buildOverlay() {
        // Fond avec degrade
        getRoot().add(new GradientRectangle()
            .originalPos(0, 0).originalSize(220, 70)
            .vertical(0x80150A2A, 0x801A0B35)
            .cornerRadius(8));

        // Bordure superieure
        getRoot().add(new GradientRectangle()
            .originalPos(0, 0).originalSize(220, 2)
            .horizontal(0x00000000, 0xFF6B2FA0)
            .cornerRadius(0));

        // Icone / prefixe
        getRoot().add(new Label(8, 10, 20, 16, "\u2726")
            .color(0xFFFFD700)
            .scale(1.0f));

        // Nom de la faction
        factionNameLabel = new Label(28, 8, 180, 18, "Aucune faction")
            .color(0xFFE0E0E0)
            .scale(1.0f)
            .shadow(true);

        // Grade
        rankLabel = new Label(8, 30, 204, 14, "Grade : ---")
            .color(0xFF9B8BCC)
            .scale(0.85f);

        // Membres en ligne
        membersLabel = new Label(8, 48, 204, 14, "Membres en ligne : 0")
            .color(0xFF6BCB77)
            .scale(0.85f);

        getRoot().add(factionNameLabel, rankLabel, membersLabel);
    }

    public void update(String faction, String rank, int onlineMembers) {
        if (factionNameLabel != null) factionNameLabel.text(faction);
        if (rankLabel != null) rankLabel.text("Grade : " + rank);
        if (membersLabel != null) membersLabel.text("Membres en ligne : " + onlineMembers);
    }
}

Exemple 3 — Overlay avec effets visuels animes

Un overlay ambiante avec un champ d'etoiles en fond et une aurora, affiche en haut de l'ecran. Ce type d'overlay est utilise pour des effets decoratifs permanents.

Java — AmbientOverlay avec Starfield et Aurora
import fr.eri.eriapi.overlay.*;
import fr.eri.eriapi.gui.components.*;
import fr.eri.eriapi.gui.anim.*;

public class AmbientOverlay extends EriOverlay {

    public AmbientOverlay() {
        super("ambient_overlay", 1920, 300);
        setAnchor(Anchor.TOP_LEFT);
        setLayer(OverlayLayer.PRE_ALL); // sous le HUD vanilla
        hideInGui(false); // reste visible meme dans les menus
        hideOnF1(true);
    }

    @Override
    protected void buildOverlay() {
        // Champ d'etoiles en fond
        getRoot().add(new Starfield()
            .originalPos(0, 0).originalSize(1920, 300)
            .starCount(60)
            .starColor(0xFFF0F2FF)
            .shootingStarChance(0.002f));

        // Aurora au-dessus
        getRoot().add(new Aurora()
            .originalPos(0, 0).originalSize(1920, 300)
            .addBand(0x306B2FA0, 0.4f, 1.0f, 100, 80)
            .addBand(0x2000E5FF, 0.6f, 0.7f, 80, 160)
            .speed(0.015f));
    }
}

Notes techniques

Scope des animations

Quand un GUI est ferme (EriGuiScreen.onGuiClosed()), le framework appelle AnimationManager.getInstance().stopGuiAnimations(). Cette methode arrete toutes les animations sans scope (scope == null). Les animations avec .scope("overlay") (ou tout autre scope non nul) sont preservees.

Regle a retenir : dans un overlay, toute animation en boucle (loop(), pingPong()) doit avoir .scope("overlay"). Les animations a usage unique (fondu d'apparition, slide) n'ont pas besoin de scope.

hideInGui

Par defaut, hideInGui(true) masque automatiquement l'overlay quand Minecraft.getMinecraft().currentScreen != null (un GUI est ouvert). Mettre a false si ton overlay doit rester visible dans les menus (effet decoratif, information persistante).

hideOnF1

Par defaut, hideOnF1(true) masque l'overlay quand mc.gameSettings.hideGUI == true (joueur appuie sur F1 pour cacher le HUD). Mettre a false uniquement pour les overlays qui ont une raison forte de toujours s'afficher (debug info, etc.).

Thread safety

Les overlays sont entierement rendus sur le thread de rendu client. Ne modifie pas les composants depuis un thread async (EriScheduler.async ou autre). Utilise EriScheduler.delay(0, () -> overlay.setMana(...)) pour rappliquer sur le thread principal si necessaire.

Performance

Les overlays sont rendus a chaque frame (60 FPS+). Evite les calculs lourds dans buildOverlay() ou dans les lambdas onUpdate. Les composants decoratifs (SmokeFog, ParticleSystem) ont des limites integrees (cellSize, maxParticles) pour controler le cout GPU.

Bonne pratique : mettre a jour via des methodes publiques

Stocke les references aux composants comme champs prives de ta classe overlay. Expose des methodes publiques (setMana(), update()) pour mettre a jour les valeurs. Ne cree pas de nouveaux composants a chaque tick — modifie seulement les proprietes des composants existants (.text(), .progress(), etc.).

OverlayMod — Systeme Modulaire

OverlayMod est le nouveau systeme d'overlay modulaire d'EriAPI. Il remplace l'approche manuelle avec EriOverlay pour les overlays complexes qui ont besoin de configuration utilisateur, de positionnement par drag & drop et de persistance automatique entre les sessions de jeu.

Chaque OverlayMod est un module autonome qui embarque :

  • Position propre — stockee en pixels de design (espace 1920x1080), convertie a l'affichage par le ScaleManager
  • Facteur de zoom propre — chaque mod a son propre scale configurable
  • Parametres configurables — via ModSettings, exposes dans l'editeur
  • Positionnement drag & drop — l'editeur integre permet a l'utilisateur de deplacer chaque mod librement
  • Persistance JSON automatique — position, scale et parametres sont sauvegardes et restaures
  • Rendu par frame avec precision nanotime — le callback onFrame() recoit le delta en nanosecondes
CLASS ABSTRACT
OverlayMod
fr.eri.eriapi.overlay.OverlayMod

Creer un OverlayMod

Java — OverlayMod minimal (affichage des PV)
import fr.eri.eriapi.overlay.OverlayMod;
import fr.eri.eriapi.gui.components.*;
import net.minecraft.client.Minecraft;
import net.minecraft.entity.player.EntityPlayer;

public class HealthOverlayMod extends OverlayMod {

    private Label healthLabel;
    private Rectangle background;

    public HealthOverlayMod() {
        super("my_health", "Health Display", 200, 40);
        setCategory("Combat");
        setDescription("Affiche les PV du joueur.");
        setDefaultPosition(0.01f, 0.9f);  // bas-gauche (x et y en fraction de l'ecran)
        setMinScale(0.5f);
        setMaxScale(2.0f);
    }

    @Override
    protected void buildOverlay() {
        background = new Rectangle();
        background.originalPos(0, 0).originalSize(200, 40);
        background.fillColor(0xAA000000).cornerRadius(6);
        getRoot().add(background);

        healthLabel = new Label("HP: 20/20");
        healthLabel.originalPos(8, 8).originalSize(184, 24);
        healthLabel.color(0xFFFF5555).scale(1.2f);
        getRoot().add(healthLabel);
    }

    @Override
    protected void onFrame(float partialTicks, long deltaNanos) {
        EntityPlayer player = Minecraft.getMinecraft().player;
        if (player == null) return;
        int hp = (int) player.getHealth();
        int maxHp = (int) player.getMaxHealth();
        healthLabel.text("HP: " + hp + "/" + maxHp);
    }
}

Enregistrement

Enregistre tes mods dans ClientProxy.init() via OverlayModManager. Comme pour OverlayManager, l'enregistrement se fait une seule fois.

Java — Enregistrement dans ClientProxy.init()
import fr.eri.eriapi.overlay.OverlayModManager;

// Dans ClientProxy.init() :
OverlayModManager.getInstance().register(new HealthOverlayMod());
OverlayModManager.getInstance().register(new DebugOverlayMod());
OverlayModManager.getInstance().register(new FactionInfoMod());
OverlayMod — reference des methodes
fr.eri.eriapi.overlay.OverlayMod
API publique
  • OverlayMod(String id, String name, int designWidth, int designHeight)
    Constructeur. id est l'identifiant unique (utilise pour la persistance JSON). name est le nom affiche dans l'editeur. designWidth et designHeight sont les dimensions de la zone de rendu en pixels de design (espace 1920x1080).
    Constructeur
  • void setCategory(String category)
    Definit la categorie affichee dans le gestionnaire (ex : "Combat", "Informations", "Debug"). Permet de regrouper les mods par theme dans l'interface de gestion.
    Configuration
  • void setDescription(String description)
    Description courte affichee dans le gestionnaire d'overlays. Doit expliquer en une phrase ce que le mod affiche.
    Configuration
  • void setDefaultPosition(float x, float y)
    Position par defaut en fraction de l'ecran (0.0 a 1.0 pour x et y). Compatible avec l'ancien format — convertit en interne en pixels de design (x * 1920, y * 1080). Exemple : (0.01f, 0.9f) = environ coin bas-gauche.
    Configuration
  • void setDefaultPositionDesign(float designX, float designY)
    Position par defaut directement en pixels de design (espace 1920x1080). Preferable a setDefaultPosition() pour un positionnement precis. Exemple : (20, 950) = 20 design-pixels depuis le bord gauche, 950 depuis le haut.
    Configuration
  • void setMinScale(float min) / setMaxScale(float max)
    Definit les limites du zoom utilisateur dans l'editeur. Valeurs typiques : min 0.5f, max 2.0f. Le scale par defaut est 1.0f.
    Configuration
  • void buildOverlay()
    Methode abstraite a implementer. Construis ici tous les composants visuels avec getRoot().add(...). Appelee une seule fois au premier rendu (et a chaque appel de rebuild()).
    A implementer
  • void buildSettings(ModSettings settings)
    Override optionnel. Declare ici les parametres configurables du mod via le builder ModSettings. Voir la section ModSettings.
    Optionnel
  • void onFrame(float partialTicks, long deltaNanos)
    Override optionnel. Appele a chaque frame de rendu (60-144+ fois par seconde). partialTicks est la fraction de tick ecoulee depuis le dernier tick (0.0 a 1.0). deltaNanos est le temps en nanosecondes depuis le dernier appel — utile pour les timers internes. C'est ici que tu mets a jour les labels, barres, etc.
    Optionnel
  • void onTick()
    Override optionnel. Appele 20 fois par seconde (chaque tick serveur). Utilise pour les mises a jour de donnees lourdes ou les synchronisations serveur. Voir onFrame vs onTick.
    Optionnel
  • void onSettingChanged(String key, Object value)
    Override optionnel. Appele automatiquement quand l'utilisateur modifie un parametre dans l'editeur. key correspond a la cle declaree dans buildSettings(). value est la nouvelle valeur (Integer, Float, Boolean ou String selon le type de parametre).
    Optionnel
  • <T> T getSetting(String key, T defaultValue)
    Lit la valeur courante d'un parametre. Si le parametre n'a pas encore ete charge (premiere session), retourne defaultValue. Le type T est infere depuis defaultValue — passe le meme type que celui declare dans buildSettings().
    Getter
  • void rebuild()
    Force la reconstruction complete des composants visuels au prochain rendu. Appelle buildOverlay() a nouveau. Utile quand un parametre change le layout (nombre de lignes, mode compact, etc.).
    Methode
  • ContainerComponent getRoot()
    Retourne le conteneur racine dans lequel ajouter les composants. Disponible depuis buildOverlay().
    Getter
OverlayMod vs EriOverlay

Utilise EriOverlay pour des overlays simples et statiques (barre de mana, HUD de faction) ou tu geres toi-meme la mise a jour via des methodes publiques. Utilise OverlayMod quand tu veux que l'utilisateur puisse configurer, deplacer et activer/desactiver l'overlay depuis un editeur integre, avec persistance automatique. Les deux systemes coexistent et peuvent etre utilises ensemble.

ModSettings — Parametres configurables

Chaque OverlayMod peut declarer des parametres configurables en implementant buildSettings(ModSettings settings). Ces parametres sont automatiquement exposes dans l'editeur GuiOverlayHub et leurs valeurs sont persistees en JSON.

Le builder ModSettings utilise une syntaxe fluente — chaque appel retourne le meme builder pour enchaîner les declarations.

CLASS
ModSettings
fr.eri.eriapi.overlay.settings.ModSettings
Java — Declaration de parametres avec buildSettings()
@Override
protected void buildSettings(ModSettings settings) {
    settings
        .separator("Sections visibles")
        .toggle("show_fps", "FPS", true)
        .toggle("show_coords", "Coordonnees", true)
        .separator("Apparence")
        .colorPicker("text_color", "Couleur du texte", 0xFFCCCCCC)
        .slider("bg_opacity", "Opacite du fond", 0f, 1f, 0.67f)
        .toggle("compact", "Mode compact", false);
}

@Override
protected void onSettingChanged(String key, Object value) {
    if ("text_color".equals(key)) {
        int color = (Integer) value;
        myLabel.color(color);
    } else if ("compact".equals(key)) {
        rebuild();  // Le layout change completement en mode compact
    }
}

// Lire une valeur dans onFrame() ou onTick() :
boolean showFps = getSetting("show_fps", true);
float opacity  = getSetting("bg_opacity", 0.67f);

Types de parametres disponibles

Methode Type retourne Description
toggle(key, label, default) Boolean Interrupteur on/off. Affiche un toggle switch dans l'editeur.
slider(key, label, min, max, default) Float Curseur a valeur flottante entre min et max. Ideal pour les opacites, tailles, vitesses.
sliderInt(key, label, min, max, default) Integer Curseur a valeur entiere. Pour les compteurs, limites de lignes, etc.
colorPicker(key, label, defaultColor) Integer (ARGB) Selecteur de couleur complet (roue, luminosite, alpha). La valeur est un entier ARGB 0xAARRGGBB.
dropdown(key, label, default, options...) String Menu deroulant avec une liste de choix. Passe les options sous forme de varargs String.
textField(key, label, default) String Champ texte libre. Utile pour des prefixes, suffixes ou noms personnalises.
keybind(key, label, defaultKey) Integer (LWJGL) Selecteur de touche. La valeur est un code LWJGL (Keyboard.KEY_*).
separator(title) Separateur visuel avec titre. Ne cree pas de parametre, sert uniquement a organiser l'interface.
info(text) Texte informatif non interactif. Utile pour des notes ou avertissements dans l'editeur.
Lire les parametres avec getSetting()

La methode getSetting(key, defaultValue) retourne toujours une valeur valide : soit la valeur sauvegardee, soit la valeur par defaut si le parametre n'a jamais ete modifie. Utilise la meme valeur par defaut dans buildSettings() et dans getSetting() pour garantir la coherence.

GuiOverlayHub — L'editeur integre

GuiOverlayHub est l'interface graphique centrale du systeme modulaire. Elle permet aux utilisateurs de positionner, configurer et activer/desactiver leurs overlays sans quitter le jeu.

GUI
GuiOverlayHub
fr.eri.eriapi.overlay.GuiOverlayHub

Fonctionnalites de l'editeur

Vue editeur (couche de base)
Drag & Drop

La couche de base de l'editeur affiche tous les overlays actifs en superposition sur l'ecran de jeu. Chaque overlay peut etre :

  • Deplace — clic + glisser pour repositionner librement
  • Zoom — molette de souris pour changer le scale
  • Selectionne — clic droit pour ouvrir le menu contextuel (parametres, reset, toggle)

Un toggle Snap to grid permet d'activer l'alignement magnetique sur une grille. Le bouton Reset All remet tous les overlays a leur position par defaut.

Bouton "Gestion" — popup de liste
Liste

Ouvre un popup listant tous les OverlayMod enregistres. Pour chaque mod :

  • Toggle d'activation/desactivation rapide
  • Categorie et description
  • Barre de recherche par nom
  • Filtrage par categorie
Bouton "Parametres" — popup de configuration
Settings

Disponible sur chaque mod selectionne (via le bouton dans l'editeur ou le menu contextuel au clic droit). Affiche tous les parametres declares dans buildSettings() avec les composants correspondants (toggle, slider, colorPicker, dropdown...). Les changements sont appliques en temps reel via onSettingChanged().

Ouvrir l'editeur

L'editeur s'ouvre par defaut avec la touche ] (configurable via les keybindings EriAPI). Il peut aussi etre ouvert programmatiquement :

Java — Ouverture programmatique
// Ouvrir l'editeur depuis du code (ex. : depuis un keybinding ou une commande)
OverlayModManager.getInstance().openEditor();

Sauvegarder

Le bouton Sauvegarder et fermer ecrit la configuration en JSON (voir Persistance JSON) et ferme l'editeur. Tant que l'utilisateur n'a pas sauvegarde, les changements sont appliques visuellement en temps reel mais pas encore persistes sur disque.

onFrame vs onTick

OverlayMod propose deux callbacks de mise a jour avec des frequences tres differentes. Bien choisir lequel utiliser impacte directement la fluidite et les performances de ton overlay.

Regle generale

onFrame() pour tout ce qui est visuel et doit etre fluide. onTick() pour les calculs lourds, les acces au monde ou les donnees synchronisees depuis le serveur.

Callback Frequence Quand l'utiliser
onFrame(partialTicks, deltaNanos) 60 a 144+ fois/s (chaque frame de rendu) Mise a jour de labels, coordonnees, FPS, animations fluides, interpolation de valeurs
onTick() 20 fois/s (toutes les 50ms) Calculs lourds, acces au biome, requetes au monde, donnees synchronisees serveur

Le parametre deltaNanos de onFrame() donne le temps exact ecoule depuis le dernier appel en nanosecondes. Il permet de creer des timers internes pour executer du code semi-lourd a intervalles precis sans passer par onTick().

Java — Timer nanotime dans onFrame() pour les mises a jour semi-lourdes
private long timer = 0L;
private static final long INTERVAL = 500_000_000L; // 500ms en nanosecondes

@Override
protected void onFrame(float partialTicks, long deltaNanos) {
    timer += deltaNanos;

    // Mise a jour legere chaque frame (fluide)
    fpsLabel.text("FPS: " + Minecraft.getDebugFPS());

    // Mise a jour lourde toutes les 500ms seulement
    if (timer >= INTERVAL) {
        timer = 0L;
        biomeLabel.text("Biome: " + getBiomeName()); // acces au monde, plus couteux
    }
}
Pourquoi onFrame() plutot que onTick() pour les coordonnees ?

Les coordonnees du joueur changent entre les ticks (le mouvement est interpole avec partialTicks). Si tu mets a jour les coordonnees dans onTick(), le texte saute par increments discrets. Avec onFrame(), le texte suit le mouvement reel du joueur de facon completement fluide.

Positionnement en design-pixels

Depuis la mise a jour du systeme modulaire, les positions des OverlayMod sont stockees en pixels de design (espace 1920x1080), identiquement a tous les composants EriAPI. C'est le ScaleManager qui convertit ces coordonnees en pixels ecran reels au moment du rendu.

Pourquoi ce changement ?

L'ancien systeme stockait les positions en fractions de l'ecran (0.0 a 1.0). En apparence independant de la resolution, ce systeme causait un probleme subtil : l'ecart visuel entre deux overlays adjacents changeait selon la resolution et le guiScale du joueur, car les positions et les tailles n'etaient pas dans le meme espace de coordonnees.

Critere Ancien systeme (fractions) Nouveau systeme (design-pixels)
Unite de position Fraction ecran (0.0 – 1.0) Pixels de design (0 – 1920 / 0 – 1080)
Unite des composants Pixels de design (ScaleManager) Pixels de design (ScaleManager) — identique
Ecart entre overlays Varie selon la resolution Constant quelque soit la resolution
Conversion au rendu fraction * displayWidth / guiScale ScaleManager.scaleXf(posXDesign)

Compatibilite ascendante

L'API publique reste 100% compatible. setDefaultPosition(float x, float y) accepte toujours des fractions et les convertit en interne :

Java — les deux methodes de positionnement
// Methode historique — fractions (0.0 a 1.0)
// Toujours valide, convert en interne : x * 1920, y * 1080
setDefaultPosition(0.01f, 0.9f);   // ≈ (19, 972) en design-pixels

// Nouvelle methode — pixels de design directs (recommandee)
// Plus precis et coherent avec le reste du systeme EriAPI
setDefaultPositionDesign(20f, 960f);  // exactement 20px a gauche, 960px en bas

Exemple — deux overlays adjacents

Java — overlays alignes avec un ecart constant
// Barre de mana en bas a gauche
public class ManaBarMod extends OverlayMod {
    public ManaBarMod() {
        super("mana_bar", "Mana", 240, 20);
        setDefaultPositionDesign(20f, 1010f); // 20px depuis la gauche, 1010px depuis le haut
    }
}

// Barre d'endurance juste en dessous, ecart de 6 design-pixels
public class StaminaMod extends OverlayMod {
    public StaminaMod() {
        super("stamina_bar", "Endurance", 240, 20);
        // 1010 (mana) + 20 (hauteur mana) + 6 (ecart) = 1036
        setDefaultPositionDesign(20f, 1036f);
    }
}
// L'ecart de 6 design-pixels est identique a toutes les resolutions.
Getters de position

getPosXDesign() et getPosYDesign() retournent la position en pixels de design. Les anciens getters getPosXPercent() et getPosYPercent() sont toujours disponibles pour la compatibilite (ils convertissent en divisant par 1920/1080).

Persistance JSON

Le systeme modulaire sauvegarde automatiquement la configuration de chaque OverlayMod dans un fichier JSON. Tu n'as rien a implementer : la persistance est entierement geree par OverlayModManager.

Emplacement du fichier

Chemin du fichier de configuration
.minecraft/config/eriapi/overlay_mods.json

Donnees persistees par mod

Donnee Type JSON Description
enabled boolean Si le mod est actif ou desactive dans le gestionnaire
posX / posY float Position en pixels de design (0..1920 / 0..1080), convertie au chargement depuis les fractions JSON existantes
scale float Facteur de zoom applique par l'utilisateur dans l'editeur
settings.* mixed Valeurs de tous les parametres declares dans buildSettings() (boolean, float, int, String)

Cycle de sauvegarde et de chargement

  • Chargement — automatique a la premiere frame de rendu, apres que tous les mods ont ete enregistres dans ClientProxy.init()
  • Sauvegarde — declenchee quand l'utilisateur clique sur "Sauvegarder et fermer" dans GuiOverlayHub
Ne pas modifier le fichier JSON manuellement

Le fichier overlay_mods.json est genere et lu par EriAPI. Modifier sa structure manuellement peut provoquer des erreurs de chargement. Si le fichier est corrompu, supprime-le — EriAPI recreera les valeurs par defaut au prochain lancement du jeu.

Exemple complet — DebugOverlayMod

Voici un exemple reel et complet : le DebugOverlayMod du mod EriniumFaction. Il affiche FPS, coordonnees, chunk, biome, direction, lumiere et memoire en temps reel. C'est un bon modele pour comprendre comment combiner onFrame(), un timer nanotime pour les donnees lourdes, et buildSettings().

Architecture du mod

  • Dimensions : 380 x 160 px (design 1920x1080)
  • Position par defaut : coin superieur gauche (0.5%, 0.5%)
  • Donnees legeres (FPS, coords, chunk, direction) — mises a jour dans onFrame() a chaque frame
  • Donnees lourdes (biome, lumiere, memoire) — mises a jour toutes les 500ms via un timer nanotime dans onFrame()
  • 7 parametres configurables : 7 toggles de visibilite, un colorPicker, un slider et un toggle compact
  • FPS colore : vert ≥60, jaune ≥30, rouge <30
Java — DebugOverlayMod complet
package fr.eriniumgroup.eriniumfaction.overlay;

import fr.eri.eriapi.gui.components.Label;
import fr.eri.eriapi.gui.components.Rectangle;
import fr.eri.eriapi.overlay.OverlayMod;
import fr.eri.eriapi.overlay.settings.ModSettings;
import net.minecraft.client.Minecraft;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.MathHelper;
import net.minecraft.world.biome.Biome;
import net.minecraft.world.chunk.Chunk;

public class DebugOverlayMod extends OverlayMod {

    // Labels mis a jour chaque frame
    private Label fpsLabel;
    private Label coordsLabel;
    private Label chunkLabel;
    private Label biomeLabel;
    private Label directionLabel;
    private Label lightLabel;
    private Label memoryLabel;

    // Fond
    private Rectangle background;

    // Timer nanotime pour les mises a jour lourdes (500ms)
    private long heavyUpdateTimer = 0L;
    private static final long HEAVY_UPDATE_INTERVAL = 500_000_000L;

    // Cache pour les valeurs lourdes
    private String cachedBiome   = "";
    private String cachedLight   = "";
    private String cachedMemory  = "";

    public DebugOverlayMod() {
        super("erinium_debug", "Debug Info", 380, 160);
        setCategory("Informations");
        setDescription("Affiche FPS, coordonnees, biome, direction et memoire.");
        setDefaultPosition(0.005f, 0.005f); // coin superieur gauche
        setMinScale(0.5f);
        setMaxScale(2.0f);
    }

    @Override
    protected void buildOverlay() {
        int w = 380;

        // Fond semi-transparent avec coins arrondis
        background = new Rectangle();
        background.originalPos(0, 0).originalSize(w, 160);
        background.fillColor(0xAA1A1A2E).cornerRadius(6);
        getRoot().add(background);

        // Creation des labels sur chaque ligne (espacement de 20px)
        int x = 8, y = 6, lineH = 20;
        fpsLabel       = createLine(x, y, w - 16); y += lineH;
        coordsLabel    = createLine(x, y, w - 16); y += lineH;
        chunkLabel     = createLine(x, y, w - 16); y += lineH;
        biomeLabel     = createLine(x, y, w - 16); y += lineH;
        directionLabel = createLine(x, y, w - 16); y += lineH;
        lightLabel     = createLine(x, y, w - 16); y += lineH;
        memoryLabel    = createLine(x, y, w - 16);

        getRoot().add(fpsLabel, coordsLabel, chunkLabel, biomeLabel,
                      directionLabel, lightLabel, memoryLabel);
    }

    private Label createLine(int x, int y, int w) {
        Label label = new Label("...");
        label.originalPos(x, y).originalSize(w, 18);
        label.color(0xFFCCCCCC).scale(0.9f);
        return label;
    }

    @Override
    protected void buildSettings(ModSettings settings) {
        settings
            .separator("Sections visibles")
            .toggle("show_fps",       "FPS",          true)
            .toggle("show_coords",    "Coordonnees",  true)
            .toggle("show_chunk",     "Chunk",        true)
            .toggle("show_biome",     "Biome",        true)
            .toggle("show_direction", "Direction",    true)
            .toggle("show_light",     "Lumiere",      true)
            .toggle("show_memory",    "Memoire",      true)
            .separator("Apparence")
            .colorPicker("text_color", "Couleur du texte", 0xFFCCCCCC)
            .slider("bg_opacity",      "Opacite du fond",  0f, 1f, 0.67f)
            .toggle("compact",         "Mode compact",     false);
    }

    @Override
    protected void onSettingChanged(String key, Object value) {
        if ("text_color".equals(key)) {
            applyTextColor((Integer) value);
        } else if ("bg_opacity".equals(key)) {
            float opacity = (Float) value;
            int alpha = (int) (opacity * 255);
            background.fillColor((alpha << 24) | 0x1A1A2E);
        } else if ("compact".equals(key) || key.startsWith("show_")) {
            rebuild(); // Le layout change (lignes cachees en mode compact)
        }
    }

    private void applyTextColor(int color) {
        if (fpsLabel       != null) fpsLabel.color(color);
        if (coordsLabel    != null) coordsLabel.color(color);
        if (chunkLabel     != null) chunkLabel.color(color);
        if (biomeLabel     != null) biomeLabel.color(color);
        if (directionLabel != null) directionLabel.color(color);
        if (lightLabel     != null) lightLabel.color(color);
        if (memoryLabel    != null) memoryLabel.color(color);
    }

    @Override
    protected void onFrame(float partialTicks, long deltaNanos) {
        Minecraft mc = Minecraft.getMinecraft();
        EntityPlayer player = mc.player;
        if (player == null || mc.world == null) return;

        heavyUpdateTimer += deltaNanos;

        // FPS — chaque frame, avec couleur adaptative
        if (fpsLabel != null && getSetting("show_fps", true)) {
            int fps = Minecraft.getDebugFPS();
            fpsLabel.text("FPS: " + fps);
            fpsLabel.color(fps >= 60 ? 0xFF55FF55 : fps >= 30 ? 0xFFFFFF55 : 0xFFFF5555);
        }

        // Coordonnees — chaque frame pour une fluidite maximale
        if (coordsLabel != null && getSetting("show_coords", true)) {
            boolean compact = getSetting("compact", false);
            coordsLabel.text(compact
                ? String.format("XYZ: %.0f / %.0f / %.0f", player.posX, player.posY, player.posZ)
                : String.format("X: %.1f  Y: %.1f  Z: %.1f", player.posX, player.posY, player.posZ));
        }

        // Chunk — chaque frame
        if (chunkLabel != null && getSetting("show_chunk", true)) {
            int cx = MathHelper.floor(player.posX) >> 4;
            int cz = MathHelper.floor(player.posZ) >> 4;
            chunkLabel.text("Chunk: " + cx + ", " + cz);
        }

        // Direction — chaque frame (rotation interpolee)
        if (directionLabel != null && getSetting("show_direction", true)) {
            float yaw = MathHelper.wrapDegrees(player.rotationYaw);
            directionLabel.text("Direction: " + getCardinalDirection(yaw)
                + " (" + String.format("%.0f", yaw) + ")");
        }

        // Infos lourdes — toutes les 500ms via timer nanotime
        if (heavyUpdateTimer >= HEAVY_UPDATE_INTERVAL) {
            heavyUpdateTimer = 0L;
            BlockPos pos = player.getPosition();

            if (getSetting("show_biome", true)) {
                cachedBiome = mc.world.getBiome(pos).getBiomeName();
            }
            if (getSetting("show_light", true)) {
                Chunk chunk = mc.world.getChunk(pos);
                cachedLight = "Block: "
                    + chunk.getLightFor(net.minecraft.world.EnumSkyBlock.BLOCK, pos)
                    + "  Sky: "
                    + chunk.getLightFor(net.minecraft.world.EnumSkyBlock.SKY, pos);
            }
            if (getSetting("show_memory", true)) {
                Runtime rt = Runtime.getRuntime();
                long used = (rt.totalMemory() - rt.freeMemory()) / 1048576L;
                long max  = rt.maxMemory() / 1048576L;
                cachedMemory = used + "MB / " + max + "MB (" + (used * 100 / max) + "%)";
            }
        }

        // Appliquer les valeurs cachees des infos lourdes
        if (biomeLabel   != null) biomeLabel.text("Biome: " + cachedBiome);
        if (lightLabel   != null) lightLabel.text("Light " + cachedLight);
        if (memoryLabel  != null) memoryLabel.text("Mem: " + cachedMemory);

        // Visibilite par setting
        if (fpsLabel       != null) fpsLabel.visible(getSetting("show_fps", true));
        if (coordsLabel    != null) coordsLabel.visible(getSetting("show_coords", true));
        if (chunkLabel     != null) chunkLabel.visible(getSetting("show_chunk", true));
        if (biomeLabel     != null) biomeLabel.visible(getSetting("show_biome", true));
        if (directionLabel != null) directionLabel.visible(getSetting("show_direction", true));
        if (lightLabel     != null) lightLabel.visible(getSetting("show_light", true));
        if (memoryLabel    != null) memoryLabel.visible(getSetting("show_memory", true));
    }

    @Override
    protected void onTick() {
        // Toute la logique de mise a jour est dans onFrame() pour la fluidite.
        // onTick() n'est pas utilise ici mais pourrait servir pour des appels reseau.
    }

    private static String getCardinalDirection(float yaw) {
        if (yaw < 0) yaw += 360f;
        if (yaw >= 337.5f || yaw < 22.5f)  return "S";
        if (yaw < 67.5f)                    return "SW";
        if (yaw < 112.5f)                   return "W";
        if (yaw < 157.5f)                   return "NW";
        if (yaw < 202.5f)                   return "N";
        if (yaw < 247.5f)                   return "NE";
        if (yaw < 292.5f)                   return "E";
        return "SE";
    }
}

Points cles de cet exemple

Timer nanotime pour les donnees lourdes

L'acces au biome (world.getBiome()), a la lumiere de chunk et a la memoire JVM sont des operations non triviales. Les executer a chaque frame (60+ fois/s) gaspillerait du CPU. Le timer heavyUpdateTimer les limite a une execution toutes les 500ms, tandis que les labels sont mis a jour depuis le cache a chaque frame — le rendu reste fluide sans surcout.

rebuild() sur les settings de layout

Quand le parametre compact ou un toggle show_* change, on appelle rebuild() plutot que de simplement changer la visibilite des labels. En mode compact, le format du texte lui-meme change ("XYZ: 10 / 64 / -30" au lieu de "X: 10.0 Y: 64.0 Z: -30.0"), ce qui necessite une reconstruction complete des composants. Si seule la visibilite changeait, un simple label.visible(false) suffirait sans rebuild().

Editeur d'overlays : aimantation automatique v1.2.0

Quand vous ouvrez l'editeur in-game (GuiOverlayHub) et que vous deplacez un overlay en le faisant glisser, le systeme d'aimantation automatique entre en jeu. L'overlay en cours de deplacement se colle ("snaps") automatiquement aux bords et centres des autres overlays ainsi qu'aux reperes fixes de l'ecran, facilitant un alignement precis sans avoir a entrer des coordonnees manuellement.

Toutes les coordonnees sont exprimees en pixels de design 1920x1080, coheremment avec le reste du systeme de positionnement EriAPI.

Types d'aimantation

Le systeme detecte 10 types d'accrochage distincts. L'aimantation se declenche quand la distance entre deux reperes est inferieure ou egale a 5 pixels de design.

Type Description Repere source Repere cible
LEFT_TO_LEFT Bord gauche aligne sur le bord gauche d'un autre overlay x de l'overlay deplace x d'un overlay voisin
LEFT_TO_RIGHT Bord gauche aligne sur le bord droit d'un autre overlay x de l'overlay deplace x + largeur d'un overlay voisin
RIGHT_TO_RIGHT Bord droit aligne sur le bord droit d'un autre overlay x + largeur de l'overlay deplace x + largeur d'un overlay voisin
RIGHT_TO_LEFT Bord droit aligne sur le bord gauche d'un autre overlay x + largeur de l'overlay deplace x d'un overlay voisin
TOP_TO_TOP Bord haut aligne sur le bord haut d'un autre overlay y de l'overlay deplace y d'un overlay voisin
TOP_TO_BOTTOM Bord haut aligne sur le bord bas d'un autre overlay y de l'overlay deplace y + hauteur d'un overlay voisin
BOTTOM_TO_BOTTOM Bord bas aligne sur le bord bas d'un autre overlay y + hauteur de l'overlay deplace y + hauteur d'un overlay voisin
BOTTOM_TO_TOP Bord bas aligne sur le bord haut d'un autre overlay y + hauteur de l'overlay deplace y d'un overlay voisin
CENTER_H Centre horizontal aligne sur le centre horizontal d'un autre overlay ou du centre ecran (960) x + largeur/2 de l'overlay deplace centre horizontal de la cible
CENTER_V Centre vertical aligne sur le centre vertical d'un autre overlay ou du centre ecran (540) y + hauteur/2 de l'overlay deplace centre vertical de la cible

Aimantation aux bords de l'ecran

En plus des autres overlays, les quatre bords de l'ecran sont des cibles d'aimantation :

  • Bord gauche — x = 0
  • Bord droit — x = 1920
  • Bord haut — y = 0
  • Bord bas — y = 1080

Le centre de l'ecran (960, 540) est egalement une cible pour les aimantations CENTER_H et CENTER_V.

Retour visuel

Deux indicateurs visuels s'affichent pendant le deplacement pour guider le positionnement :

Lignes guides (cyan)

Quand au moins un accrochage est actif, une ligne guide semi-transparente de couleur cyan s'affiche sur l'axe de l'aimantation :

  • Une ligne verticale pour un accrochage sur l'axe X (bords gauche/droit ou centre horizontal).
  • Une ligne horizontale pour un accrochage sur l'axe Y (bords haut/bas ou centre vertical).

Ces lignes couvrent toute la largeur ou hauteur de l'ecran afin d'etre visibles quelle que soit la position de l'overlay. Elles disparaissent immediatement quand l'overlay est relache ou quand le snap n'est plus actif.

Indicateur de chevauchement (rouge)

Si l'overlay deplace se superpose (au moins partiellement) a un autre overlay pendant le drag, il prend une teinte rouge semi-transparente. Cela signale qu'un chevauchement est en cours. L'overlay peut tout de meme etre pose a cet endroit — c'est un avertissement visuel, pas un blocage.

Desactiver l'aimantation (touche Shift)

Pour deplacer un overlay librement sans aucune aimantation, maintenez la touche Shift pendant le drag. L'overlay suit alors exactement le deplacement de la souris pixel par pixel, sans aucun accrochage ni correction de position.

Conseil d'utilisation

Utilisez le drag normal (sans Shift) pour aligner les overlays entre eux ou sur les bords de l'ecran. Utilisez Shift uniquement quand vous avez besoin d'une position tres specifique qui ne correspond a aucun repere de grille.