-
Bug
-
Resolution: Unresolved
-
None
-
1.21.1
-
None
-
Community Consensus
-
Data Packs, Mob spawning
-
Low
-
Platform
This issue is about the ability for datapacks to spawns mobs during the world-gen phase, (referred to as `creature` spawns in biome files.) This does not affect `ambient` or `monster` spawns, which seem to play by different rules.
Currently it's practically impossible for datapacks to make any mob (other than striders) spawn naturally in the nether.
When some do manage to spawn, they do so underneath the bedrock floor.
Reproduction
I was trying to make a datapack that would cause Allays to naturally spawn in Soul Sand Valley biomes.
Attached is a pack that should be correct for that purpose; it contains a modified version of `/data/minecraft/worldgen/biome/soul_sand_valley.json` with this data added to it: (spawn rate boosted to make it easier to test)
{ "spawners": { "creature": [ { "type": "minecraft:allay", "maxCount": 10, "minCount": 1, "weight": 600 } ] } }
1. Make a new creative world using the provided datapack. (I recommend the seed: `-8347975953351695516`)
2. Enter the Nether. Use `/locate biome minecraft:soul_sand_valley` and teleport there.
3. Explore a bit: *You will not be able to find any Allay.*
4. While exploring, try using `/tp @s @n[type=minecraft:allay]` on intervals, in order to locate one. When you manage to find any, you'll find that *all allays have spawned underneath the bedrock floor.*
(Finding any is highly dependent on terrain generation. With the seed above, `/tp @s 353 -3 168`, is a guaranteed success.)
Additional informations
I tried this with a few other mob types, and not one of them managed to spawn at all. Although, only the allays managed to spawn below bedrock.
I made sure my data format was correct by trying to spawn allays in overworld biomes. All spawns appeared normal there.
Code review
(Using mojmap)
It looks like all natural mobs attempt to spawn lower than they should. Attempts that should be successful instead end up *1 block deep inside the ground* causing the mob's hitbox to invariably collide with solid blocks.
Striders circumvent this issue due to their exceptional spawning rules, which allow them to spawn 1 block deep below the lava surface.
Class: `net.minecraft.world.level.NaturalSpawner`
private static BlockPos getTopNonCollidingPos(LevelReader levelReader, EntityType<?> entityType, int i, int j) { int k = levelReader.getHeight(SpawnPlacements.getHeightmapType(entityType), i, j); BlockPos.MutableBlockPos mutableBlockPos = new BlockPos.MutableBlockPos(i, k, j); // Only affects the nether dimension if (levelReader.dimensionType().hasCeiling()) { // 1st loop's condition does not check the altitude; it will fall below bedrock if no air block is encountered on this column. do { mutableBlockPos.move(Direction.DOWN); } while (!levelReader.getBlockState(mutableBlockPos).isAir()); // 2nd loop's condition only stops once the current blockpos is *inside* the ground, it should check the block below instead. // The 1st iteration of `do while` skips the altitude check, and could also fall below bedrock. do { mutableBlockPos.move(Direction.DOWN); } while (levelReader.getBlockState(mutableBlockPos).isAir() && mutableBlockPos.getY() > levelReader.getMinBuildHeight()); } return SpawnPlacements.getPlacementType(entityType).adjustSpawnPosition(levelReader, mutableBlockPos.immutable()); }
Proposed changes
private static BlockPos getTopNonCollidingPos(LevelReader levelReader, EntityType<?> entityType, int i, int j) { int k = levelReader.getHeight(SpawnPlacements.getHeightmapType(entityType), i, j); /*+*/ int yMin = levelReader.getMinBuildHeight(); BlockPos.MutableBlockPos mutableBlockPos = new BlockPos.MutableBlockPos(i, k, j); if (levelReader.dimensionType().hasCeiling()) { do { mutableBlockPos.move(Direction.DOWN); } /*- while (!levelReader.getBlockState(mutableBlockPos).isAir());*/ /*+*/ while (!levelReader.getBlockState(mutableBlockPos).isAir() && mutableBlockPos.getY() > yMin); /*- do*/ /*+*/ while (levelReader.getBlockState(mutableBlockPos.below()).isAir() && mutableBlockPos.getY() > yMin) { mutableBlockPos.move(Direction.DOWN); } /*- while (levelReader.getBlockState(mutableBlockPos).isAir() && mutableBlockPos.getY() > levelReader.getMinBuildHeight());*/ } return SpawnPlacements.getPlacementType(entityType).adjustSpawnPosition(levelReader, mutableBlockPos.immutable()); }
With this change, strider spawn rules will also need to be adjusted so they are allowed to spawn on the surface of the lava.
Class: `net.minecraft.world.entity.SpawnPlacementTypes`
public static final SpawnPlacementType IN_LAVA = (levelReader, blockPos, entityType) -> { if (entityType == null || !levelReader.getWorldBorder().isWithinBounds(blockPos)) { return false; } /*- return levelReader.getFluidState(blockPos).is(FluidTags.LAVA);*/ /*+*/ return levelReader.getFluidState(blockPos.below()).is(FluidTags.LAVA); };