Appearance
CURSUS DE DÉVELOPPEMENT DE MODS MINECRAFT — NIVEAU AVANCÉ
Module 9: La Génération de Minerais (Ore Generation)
Maîtriser l'intégration des structures géologiques via les Configured et Placed Features
1. Le Nouveau Paradigme de Génération du Monde (Worldgen)
Depuis les versions 1.18+, Mojang a complètement réécrit le moteur de génération de terrain. L'ancienne méthode qui consistait à injecter du code Java sauvage en plein milieu de la génération des chunks est obsolète. Désormais, le Worldgen utilise une architecture purement pilotée par les registres de données dynamiques à l'aide de fichiers JSON complexes.
Pour ajouter un minerai de manière propre et compatible avec d'autres mods, vous devez diviser votre logique en trois entités distinctes :
La Configured Feature (Caractéristique Configurée) : Elle définit le QUOI. Quel est le bloc cible à remplacer (ex : de la roche), quel est votre bloc de minerai personnalisé, et quelle est la taille maximale de la veine (le filon).
La Placed Feature (Caractéristique Placée) : Elle définit le OÙ et COMMENT. À quelles hauteurs (coordonnées Y) le minerai peut-il apparaître, combien de veines faut-il tenter de générer par chunk, et selon quelle courbe de distribution spatiale (uniforme ou en triangle).
Le Forge Biome Modifier (Modificateur de Biome) : Il agit comme une agrafeuse. Il prend votre
PlacedFeatureet l'injecte dynamiquement dans les biomes spécifiés (ou dans toutes les dimensions comme l'Overworld).
2. Étape 1: Déclaration des clés de Registres en Java
Puisque ces objets appartiennent au système de registres dynamiques (Registry), nous devons utiliser des clés de ressources (ResourceKey) pour permettre au Datagen et au moteur de jeu de lier nos objets Java aux structures JSON sous-jacentes.
Fichier : src/main/java/com/nomauteur/modid/worldgen/ModConfiguredFeatures.java
java
package com.nomauteur.modid.worldgen;
import com.nomauteur.modid.ModId;
import com.nomauteur.modid.registry.ModBlocks;
import net.minecraft.core.registries.Registries;
import net.minecraft.data.worldgen.BootstapContext;
import net.minecraft.resources.ResourceKey;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.tags.BlockTags;
import net.minecraft.world.level.levelgen.feature.ConfiguredFeature;
import net.minecraft.world.level.levelgen.feature.Feature;
import net.minecraft.world.level.levelgen.feature.configurations.OreConfiguration;
import net.minecraft.world.level.levelgen.structure.templatesystem.TagMatchTest;
import java.util.List;
public class ModConfiguredFeatures {
// Clé de ressource unique servant d'identifiant pour la configuration du filon
public static final ResourceKey<ConfiguredFeature<?, ?>> OVERWORLD_AMETHYSTE_ORE_KEY =
registerKey("amethyste_ore");
public static void bootstrap(BootstapContext<ConfiguredFeature<?, ?>> context) {
// Définition des règles de remplacement (Remplacer la pierre standard de l'Overworld)
OreConfiguration.TargetBlockState targetOverworldStone = OreConfiguration.target(
new TagMatchTest(BlockTags.STONE_ORE_REPLACEABLES),
ModBlocks.AMETHYSTE_ORE.get().defaultBlockState()
);
// Taille maximale du filon (ex: 7 blocs par veine)
List<OreConfiguration.TargetBlockState> overworldAmethysteOres = List.of(targetOverworldStone);
// Enregistrement de la configuration dans le contexte de démarrage du Worldgen
context.register(OVERWORLD_AMETHYSTE_ORE_KEY, new ConfiguredFeature<>(
Feature.ORE,
new OreConfiguration(overworldAmethysteOres, 7)
));
}
public static ResourceKey<ConfiguredFeature<?, ?>> registerKey(String name) {
return ResourceKey.create(Registries.CONFIGURED_FEATURE, new ResourceLocation(ModId.MOD_ID, name));
}
}3. Étape 2: Définition des Règles de Placement Spatiales
La configuration étant faite, nous créons la Placed Feature. C'est ici que l'on configure des notions mathématiques comme la hauteur sous forme de VerticalAnchor.
Fichier : src/main/java/com/nomauteur/modid/worldgen/ModPlacedFeatures.java
java
package com.nomauteur.modid.worldgen;
import com.nomauteur.modid.ModId;
import net.minecraft.core.Holder;
import net.minecraft.core.HolderGetter;
import net.minecraft.core.registries.Registries;
import net.minecraft.data.worldgen.BootstapContext;
import net.minecraft.resources.ResourceKey;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.level.levelgen.VerticalAnchor;
import net.minecraft.world.level.levelgen.feature.ConfiguredFeature;
import net.minecraft.world.level.levelgen.feature.PlacedFeature;
import net.minecraft.world.level.levelgen.placement.*;
import java.util.List;
public class ModPlacedFeatures {
public static final ResourceKey<PlacedFeature<?>> OVERWORLD_AMETHYSTE_ORE_PLACED_KEY =
registerKey("amethyste_ore_placed");
public static void bootstrap(BootstapContext<PlacedFeature<?>> context) {
HolderGetter<ConfiguredFeature<?, ?>> configuredFeatures = context.lookup(Registries.CONFIGURED_FEATURE);
// Récupération de la Configured Feature créée à l'étape 1
Holder<ConfiguredFeature<?, ?>> configuredFeatureHolder =
configuredFeatures.getOrThrow(ModConfiguredFeatures.OVERWORLD_AMETHYSTE_ORE_KEY);
// Configuration des modificateurs de placement :
// - 9 tentatives de génération par chunk (CountPlacement)
// - Distribution en forme de Triangle entre la couche Y = -64 et Y = 80
context.register(OVERWORLD_AMETHYSTE_ORE_PLACED_KEY, new PlacedFeature(
configuredFeatureHolder,
orePlacement(
CountPlacement.of(9),
HeightRangePlacement.triangle(VerticalAnchor.absolute(-64), VerticalAnchor.absolute(80))
)
));
}
public static ResourceKey<PlacedFeature<?>> registerKey(String name) {
return ResourceKey.create(Registries.PLACED_FEATURE, new ResourceLocation(ModId.MOD_ID, name));
}
// Méthodes utilitaires indispensables pour calquer le comportement des minerais vanilla
private static List<PlacementModifier> orePlacement(PlacementModifier count, PlacementModifier height) {
return List.of(count, InSquarePlacement.spread(), height, BiomeFilter.biome());
}
}Comprendre les types de distributions verticales :
HeightRangePlacement.uniform: Le minerai a exactement les mêmes chances d'apparaître à toutes les hauteurs comprises entre les bornes minimale et maximale.HeightRangePlacement.triangle: Le taux d'apparition culmine au centre exact de l'intervalle spécifié et s'estompe progressivement vers les extrémités (idéal pour centraliser un minerai rare à une couche stratégique, comme le Diamant en Y=-54).
4. Étape 3: Automatisation via le Datagen (Le Registre Dynamique)
Pour injecter ces configurations dans l'arbre d'assets sans écrire de fichiers JSON à la main, nous créons un fournisseur de données spécialisé nommé DatapackBuiltinEntriesProvider.
Fichier : src/main/java/com/nomauteur/modid/datagen/ModWorldGenProvider.java
java
package com.nomauteur.modid.datagen;
import com.nomauteur.modid.ModId;
import com.nomauteur.modid.worldgen.ModConfiguredFeatures;
import com.nomauteur.modid.worldgen.ModPlacedFeatures;
import net.minecraft.core.HolderLookup;
import net.minecraft.core.RegistrySetBuilder;
import net.minecraft.core.registries.Registries;
import net.minecraft.data.PackOutput;
import net.minecraftforge.common.data.DatapackBuiltinEntriesProvider;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
public class ModWorldGenProvider extends DatapackBuiltinEntriesProvider {
// Le RegistrySetBuilder permet d'enregistrer les fonctions de bootstrap du Worldgen
public static final RegistrySetBuilder BUILDER = new RegistrySetBuilder()
.add(Registries.CONFIGURED_FEATURE, ModConfiguredFeatures::bootstrap)
.add(Registries.PLACED_FEATURE, ModPlacedFeatures::bootstrap);
public ModWorldGenProvider(PackOutput output, CompletableFuture<HolderLookup.Provider> registries) {
super(output, registries, BUILDER, Set.of(ModId.MOD_ID));
}
}Mise à jour requise dans votre classe principale ModDataGenerators :
Pour que ce fournisseur de génération de monde s'exécute, retournez dans votre classe globale de Datagen (ModDataGenerators enregistrée au module précédent) et ajoutez cette ligne à l'intérieur du bloc conditionnel if (event.includeServer());:
java
generator.addProvider(true, new ModWorldGenProvider(packOutput, lookupProvider));5. Étape 4: L'injection finale dans les Biomes (Biome Modifiers)
Minecraft Forge intègre un outil révolutionnaire appelé les Biome Modifiers. C'est un système purement basé sur des fichiers JSON de configuration qui évite d'altérer les fichiers du jeu de base, garantissant qu'aucun crash n'aura lieu si un autre mod ajoute lui aussi ses propres structures.
Ce fichier doit être écrit manuellement ou sorti via vos générateurs, à l'emplacement précis suivant :
src/main/resources/data/[modid]/forge/biome_modifier/add_amethyste_ore.json
json
{
"type": "forge:add_features",
"biomes": "#minecraft:is_overworld",
"features": "modid:amethyste_ore_placed",
"step": "underground_ores"
}Explication des clés du Biome Modifier :
type: L'action à mener. Ici,forge:add_featuresindique au chargeur de mod d'ajouter une structure.biomes: Le pointeur de sélection. Utiliser un tag vanilla comme#minecraft:is_overworldassure que votre minerai apparaîtra dans absolument tous les biomes de la surface (y compris les biomes moddés issus d'autres créateurs).features: Le chemin d'accès vers laPlaced Featureque vous avez enregistrée à l'étape 2.step: L'étape d'enchaînement logique du moteur graphique. Les minerais doivent impérativement s'insérer durant la phaseunderground_orespour être incorporés sous terre avant la génération des grottes ou des structures de surface.
6. Résolution des problèmes fréquents (Guide d'autonomie)
Mon minerai n'apparaît nulle part en jeu, pourquoi ?
Vérifiez vos coordonnées de hauteur : Si vous configurez une génération en triangle entre Y=-64 et Y=80 mais que vous cherchez uniquement au niveau de la mer (Y=64), la densité sera très faible. Utilisez la commande en jeu
/fillpour dégager une zone et inspecter la roche.Assurez-vous d'avoir exécuté la tâche Gradle : Lancez la commande
runDatapour créer physiquement les structures JSON sous-jacentes dans vos dossiers générés.
Comment ajouter mon minerai dans le Nether ou l'End ?
Il vous suffit de modifier l'étape 1 en changeant le filtre de remplacement. Pour le Nether, utilisez le tag de remplacement BlockTags.NETHER_CARVER_REPLACEABLES (pour cibler la Netherrack) et modifiez le fichier JSON de modificateur de biome en remplaçant la ligne biomes par le tag #minecraft:is_nether.