I will use Forge 47.1.0 for Minecraft 1.20.1 as the example (offical mappings channel).
In MinecraftServer$createLevels, vanilla use the following code to add spawners for overworld:
List<CustomSpawner> list = ImmutableList.of(new PhantomSpawner(), new PatrolSpawner(), new CatSpawner(), new VillageSiege(), new WanderingTraderSpawner(serverleveldata));
I think it useful to post an event here to add spawners for overworld, and other dimensions. For example:
List<CustomSpawner> list = ImmutableList.of(new PhantomSpawner(), new PatrolSpawner(), new CatSpawner(), new VillageSiege(), new WanderingTraderSpawner(serverleveldata));
//change begin
AddCustomSpawnersEvent event = new AddCustomSpawnersEvent(ImmutableList.<CustomSpawner>builder().addAll(list), LevelStem.OVERWORLD);
MinecraftForge.EVENT_BUS.post(event);
LevelStem levelstem = registry.get(LevelStem.OVERWORLD);
ServerLevel serverlevel = new ServerLevel(this, this.executor, this.storageSource, serverleveldata, Level.OVERWORLD, levelstem, p_129816_, flag, j, event.getCustomSpawnerBuilder().build(), true, (RandomSequences)null);
//change end
//...
for(Map.Entry<ResourceKey<LevelStem>, LevelStem> entry : registry.entrySet()) {
ResourceKey<LevelStem> resourcekey = entry.getKey();
if (resourcekey != LevelStem.OVERWORLD) {
ResourceKey<Level> resourcekey1 = ResourceKey.create(Registries.DIMENSION, resourcekey.location());
DerivedLevelData derivedleveldata = new DerivedLevelData(this.worldData, serverleveldata);
//change begin
AddCustomSpawnersEvent event = new AddCustomSpawnersEvent(ImmutableList.<CustomSpawner>builder(), resourcekey);
MinecraftForge.EVENT_BUS.post(event);
ServerLevel serverlevel1 = new ServerLevel(this, this.executor, this.storageSource, derivedleveldata, resourcekey1, entry.getValue(), p_129816_, flag, j, event.getCustomSpawnerBuilder().build(), false, randomsequences);
//change end
worldborder.addListener(new BorderChangeListener.DelegateBorderChangeListener(serverlevel1.getWorldBorder()));
this.levels.put(resourcekey1, serverlevel1);
net.minecraftforge.common.MinecraftForge.EVENT_BUS.post(new net.minecraftforge.event.level.LevelEvent.Load(levels.get(resourcekey)));
}
}
Now the only way I can add custom spawners is to inject constructor of ServerLevel and modify final member customSpawners.