FAQ & Pièges courants
Les erreurs fréquentes et comment les éviter.
Installation
Le jeu crashe au démarrage avec ClassNotFoundException pour une classe de la lib
Cause : la lib n'est pas chargée, ou fg.deobf() est absent.
Solution :
- Vérifier que
mavenLocal()est dans le blocrepositories {}debuild.gradle. - Vérifier que la dépendance utilise
fg.deobf():implementation fg.deobf("ca.tawess123.apocalyinterface:apocalyinterfacelib:1.0.0") - Exécuter
gradlew publishToMavenLocaldans le répertoire d'ApocalyInterfaceLib.
Le serveur dédié crashe avec ModLoadingException sur la lib
Cause : la dépendance dans mods.toml est déclarée avec side="BOTH".
Solution : la lib est client-only. Déclarer side="CLIENT" dans mods.toml :
[[dependencies.monmod]]
modId="apocalyinterfacelib"
mandatory=true
versionRange="[1.0,)"
ordering="AFTER"
side="CLIENT" ← obligatoire, pas "BOTH"
Écrans et layout
Mon écran s'ouvre mais est vide / rien ne s'affiche
Causes fréquentes :
buildLayoutoublie d'appelerroot.add(...)— vérifier que tu ajoutes bien des widgets àroot.- Le panneau est hors écran — la largeur demandée est trop grande. Réduire
panelWidthdans le constructeur.
J'utilise ApocalyContainerScreen mais les slots sont décalés / mal positionnés
Cause : les positions de slots dans le AbstractContainerMenu sont calculées incorrectement par rapport aux dimensions du panneau.
Solution : les positions slot.x et slot.y sont relatives au coin supérieur gauche du panneau (leftPos, topPos). Le coin du contenu commence après le header (22 px) + le divider + son offset.
Référence pour une grille en haut du contenu :
static final int SLOT_ORIGIN_X = 11; // PANEL_MARGIN(10) + 1
static final int SLOT_ORIGIN_Y = 32; // header(22) + divider(4) + gap(6)
Mes boutons dans une ScrollableColumn ont le scroll remis à zéro à chaque update()
Cause : la ScrollableColumn est créée dans buildLayout, pas dans le constructeur.
Solution : construire la ScrollableColumn dans le constructeur de l'écran et l'ajouter (sans la recréer) dans buildLayout :
// Correct ✓
private final ScrollableColumn buttonScroll;
public MonEcran() {
super(...);
buttonScroll = ScrollableColumn.builder().fixedHeight(116).build();
buttonScroll.add(...);
}
@Override
protected void buildLayout(Column root) {
root.add(buttonScroll); // ajouter, pas recréer
}
buildLayout reçoit une Row et pas une Column dans mon ApocalyContainerScreen
C'est normal. ApocalyContainerScreen.buildLayout reçoit une Row (pas une Column) pour permettre un layout horizontal dans la zone de contenu. Si tu veux un layout vertical, imbrique une Column dans la Row :
@Override
protected void buildLayout(Row root) {
Column col = Column.create();
col.add(monLabel);
col.add(monSlotGrid);
root.add(col);
}
Données et rafraîchissement
Mon Label avec DataSource.of(champ) n'affiche pas la nouvelle valeur après update()
Cause : DataSource.of(champ) capture la valeur au moment de l'appel, pas une référence au champ. Si champ a changé entre deux buildLayout, la valeur capturée lors du premier buildLayout est obsolète.
Solution : DataSource.of(champ) est correct si tu appelles update() après avoir mis à jour champ. L'update() relance buildLayout, qui relit le champ à jour :
public void refresh(int newPoints) {
this.points = newPoints; // ← mettre à jour le champ D'ABORD
update(); // ← puis relancer buildLayout
}
Je veux une valeur qui se met à jour en continu (timer, HP) sans appeler update()
Utiliser un lambda DataSource — il est réévalué à chaque frame :
// Mis à jour automatiquement à chaque rendu
ProgressBar.builder()
.value(() -> player.getHealth() / player.getMaxHealth())
.build();
Confusion Screen vs ContainerScreen
J'utilise ApocalyScreen mais j'ai besoin de slots interactifs
Solution : passer à ApocalyContainerScreen. Les slots interactifs nécessitent une synchronisation réseau via AbstractContainerMenu — incompatible avec ApocalyScreen.
Voir guide Screen vs ContainerScreen et recette marchand à slots.
J'ai utilisé MenuType = null dans mon AbstractContainerMenu comme dans le mod démo
Problème : TestContainerMenu utilise null comme raccourci pour les tests visuels client-only. En production, Forge envoie un packet réseau pour ouvrir le container — il a besoin d'un MenuType enregistré.
Solution : enregistrer un MenuType via DeferredRegister et ouvrir le container avec NetworkHooks.openScreen() côté serveur. Voir la recette marchand à slots pour l'exemple complet.
Sécurité
Je masque un bouton avec visibleIf(false) — est-ce que le joueur peut quand même l'utiliser ?
Oui. visibleIf(false) est purement cosmétique — il masque l'affichage et désactive le clic dans l'UI. Mais un joueur malveillant peut toujours envoyer la commande ou le packet correspondant directement, sans passer par l'UI.
Toujours valider les permissions côté serveur, indépendamment de l'UI :
// Côté serveur — toujours re-vérifier, même si le bouton est masqué côté client
if (!player.hasPermission(Permission.CLAN_KICK)) {
return; // ou envoyer un message d'erreur
}
i18n
Mon widget affiche la clé brute (ex. monmod.label.titre) au lieu du texte traduit
Cause : la clé est absente du fichier de langue, ou le fichier de langue est mal placé.
Solution :
- Vérifier le chemin :
src/main/resources/assets/<modid>/lang/en_us.json - Vérifier que
<modid>correspond exactement aumodIddansmods.toml(sensible à la casse) - S'assurer que
en_us.jsonest présent — c'est la langue de fallback de Forge
L'affichage de la clé brute n'est pas un crash — c'est le fallback intentionnel de LocaleResolver pour faciliter le développement.
Mon Label.value() s'affiche mais n'est pas substitué dans la clé i18n (%s visible)
Cause : la valeur .value() est substituée uniquement si la clé i18n contient %s. Vérifier le fichier de langue :
{ "monmod.label.clan": "Clan : %s" }
Si la clé ne contient pas %s, la valeur est concaténée après le texte traduit.
Imports
Import depuis ca.tawess123.apocalyinterface.core.* — erreur de compilation ou comportement inattendu
Cause : le package core/ est interne à la lib. Seul api/ est public et stable.
Solution : ne jamais importer depuis core/. Tous les widgets, conteneurs et classes utilitaires sont dans ca.tawess123.apocalyinterface.api.*.