Make enormous placement a wrapper around regular placement modifiers, simplifying code

This commit is contained in:
DBotThePony 2025-03-09 12:59:36 +07:00
parent 7736762c86
commit 674c875249
Signed by: DBot
GPG Key ID: DCC23B5715498507
7 changed files with 148 additions and 251 deletions

View File

@ -8,7 +8,6 @@ import net.minecraft.tags.BiomeTags
import net.minecraft.tags.BlockTags
import net.minecraft.util.valueproviders.ClampedNormalFloat
import net.minecraft.util.valueproviders.ConstantFloat
import net.minecraft.util.valueproviders.ConstantInt
import net.minecraft.util.valueproviders.UniformFloat
import net.minecraft.util.valueproviders.UniformInt
import net.minecraft.world.level.levelgen.GenerationStep
@ -33,10 +32,8 @@ import ru.dbotthepony.mc.otm.worldgen.placement.StandardDeviationHeightProvider
import ru.dbotthepony.mc.otm.registry.game.MBlocks
import ru.dbotthepony.mc.otm.registry.data.MWorldGenFeatures
import ru.dbotthepony.mc.otm.worldgen.feature.BlackHolePlacerFeature
import ru.dbotthepony.mc.otm.worldgen.feature.DebugPlacerFeature
import ru.dbotthepony.mc.otm.worldgen.placement.AbstractEnormousPlacement
import ru.dbotthepony.mc.otm.worldgen.placement.EnormousPlacement
import ru.dbotthepony.mc.otm.worldgen.placement.EllipsoidPlacement
import ru.dbotthepony.mc.otm.worldgen.placement.EnormousEllipsoidPlacement
import ru.dbotthepony.mc.otm.worldgen.placement.WormPlacement
private object ConfiguredFeatures {
@ -118,36 +115,34 @@ fun registerPlacedFeatures(context: BootstrapContext<PlacedFeature>) {
context.register(PlacedFeatures.WORM_TRITANIUM, PlacedFeature(
configured.getOrThrow(ConfiguredFeatures.TRITANIUM_ORE_SMALL),
listOf(
WormPlacement(
parameters = AbstractEnormousPlacement.Parameters(
chunkScanRange = 24,
seedMix = 9284343575495L,
prePlacementModifiers = listOf(
RarityFilter.onAverageOnceEvery(300),
InSquarePlacement.spread(),
HeightRangePlacement.of(StandardDeviationHeightProvider(VerticalAnchor.absolute(-40), 15.0)),
EnormousPlacement(
chunkScanRange = 24,
seedMix = 9284343575495L,
children = listOf(
RarityFilter.onAverageOnceEvery(300),
InSquarePlacement.spread(),
HeightRangePlacement.of(StandardDeviationHeightProvider(VerticalAnchor.absolute(-40), 15.0)),
WormPlacement(
length = UniformInt.of(120, 400),
turnRateXY = WormPlacement.normalDistributedTurnRate(10f),
turnRateXZ = WormPlacement.normalDistributedTurnRate(60f),
turnSpeedXZ = WormPlacement.constantTurnRate(10f),
turnSpeedXY = WormPlacement.constantTurnRate(4f),
turnChanceXY = BooleanProvider.Unbiased(6),
turnChanceXZ = BooleanProvider.BiasedLinear(0.6f, 5),
maxTravelUp = 16,
maxTravelDown = 16,
),
postPlacementModifiers = listOf(
EllipsoidPlacement(
count = UniformInt.of(15, 30),
xLength = ConstantFloat.of(14f),
yLength = ConstantFloat.of(14f),
zLength = ConstantFloat.of(14f),
x = ClampedNormalFloat.of(0f, 0.4f, -1f, 1f),
y = ClampedNormalFloat.of(0f, 0.4f, -1f, 1f),
z = ClampedNormalFloat.of(0f, 0.4f, -1f, 1f),
)
EllipsoidPlacement(
count = UniformInt.of(15, 30),
xLength = ConstantFloat.of(14f),
yLength = ConstantFloat.of(14f),
zLength = ConstantFloat.of(14f),
x = ClampedNormalFloat.of(0f, 0.4f, -1f, 1f),
y = ClampedNormalFloat.of(0f, 0.4f, -1f, 1f),
z = ClampedNormalFloat.of(0f, 0.4f, -1f, 1f),
)
),
length = UniformInt.of(120, 400),
turnRateXY = WormPlacement.normalDistributedTurnRate(10f),
turnRateXZ = WormPlacement.normalDistributedTurnRate(60f),
turnSpeedXZ = WormPlacement.constantTurnRate(10f),
turnSpeedXY = WormPlacement.constantTurnRate(4f),
turnChanceXY = BooleanProvider.Unbiased(6),
turnChanceXZ = BooleanProvider.BiasedLinear(0.6f, 5),
maxTravelUp = 16,
maxTravelDown = 16,
)
)
)
))
@ -164,23 +159,23 @@ fun registerPlacedFeatures(context: BootstrapContext<PlacedFeature>) {
context.register(PlacedFeatures.DILITHIUM, PlacedFeature(
ore,
listOf(
EnormousEllipsoidPlacement(
parameters = AbstractEnormousPlacement.Parameters(
chunkScanRange = 5,
seedMix = 237483209523709234L,
prePlacementModifiers = listOf(
RarityFilter.onAverageOnceEvery(120),
InSquarePlacement.spread(),
HeightRangePlacement.of(StandardDeviationHeightProvider(VerticalAnchor.absolute(0), 15.0)),
),
EnormousPlacement(
chunkScanRange = 6,
seedMix = 237483209523709234L,
children = listOf(
RarityFilter.onAverageOnceEvery(120),
InSquarePlacement.spread(),
HeightRangePlacement.of(StandardDeviationHeightProvider(VerticalAnchor.absolute(0), 15.0)),
EllipsoidPlacement(
x = ringularity,
y = ringularity,
z = ringularity,
count = UniformInt.of(8000, 16000),
xLength = UniformFloat.of(30f, 70f),
yLength = UniformFloat.of(40f, 90f),
zLength = UniformFloat.of(30f, 70f),
)
),
x = ringularity,
y = ringularity,
z = ringularity,
count = UniformInt.of(8000, 16000),
xLength = UniformFloat.of(30f, 70f),
yLength = UniformFloat.of(40f, 90f),
zLength = UniformFloat.of(30f, 70f),
)
)
))

View File

@ -5,7 +5,7 @@ import net.minecraft.world.level.levelgen.placement.PlacementModifierType
import net.neoforged.bus.api.IEventBus
import ru.dbotthepony.mc.otm.registry.MDeferredRegister
import ru.dbotthepony.mc.otm.worldgen.placement.EllipsoidPlacement
import ru.dbotthepony.mc.otm.worldgen.placement.EnormousEllipsoidPlacement
import ru.dbotthepony.mc.otm.worldgen.placement.EnormousPlacement
import ru.dbotthepony.mc.otm.worldgen.placement.WormPlacement
object MPlacementModifiers {
@ -15,7 +15,7 @@ object MPlacementModifiers {
registry.register(bus)
}
val ENORMOUS by registry.register("enormous") { PlacementModifierType { EnormousPlacement.CODEC } }
val ELLIPSOID_PLACEMENT by registry.register("ellipsoid") { PlacementModifierType { EllipsoidPlacement.CODEC } }
val ENORMOUS_ELLIPSOID_PLACEMENT by registry.register("enormous_ellipsoid") { PlacementModifierType { EnormousEllipsoidPlacement.CODEC } }
val WORM_PLACEMENT by registry.register("worm") { PlacementModifierType { WormPlacement.CODEC } }
}

View File

@ -25,14 +25,41 @@ import kotlin.math.roundToInt
* This placement modifier is designed to be terminal; other placement modifiers after this MUST NOT be placed
*/
data class EllipsoidPlacement(
override val x: FloatProvider,
override val z: FloatProvider,
override val y: FloatProvider,
override val count: IntProvider,
override val xLength: FloatProvider,
override val zLength: FloatProvider,
override val yLength: FloatProvider,
) : PlacementModifier(), IEllipsoidPlacement {
/**
* X position within ellipsoid sampler, expected to be in range -1 .. 1
*/
val x: FloatProvider,
/**
* Z position within ellipsoid sampler, expected to be in range -1 .. 1
*/
val z: FloatProvider,
/**
* Y position within ellipsoid sampler, expected to be in range -1 .. 1
*/
val y: FloatProvider,
/**
* Total amount of block positions to generate
*/
val count: IntProvider,
/**
* Ellipsoid size sampler on X axis
*/
val xLength: FloatProvider,
/**
* Ellipsoid size sampler on Z axis
*/
val zLength: FloatProvider,
/**
* Ellipsoid size sampler on Y axis
*/
val yLength: FloatProvider,
) : PlacementModifier() {
init {
require(xLength.minValue >= 1f) { "Bad ellipsoid x minimal size: $xLength" }
require(zLength.minValue >= 1f) { "Bad ellipsoid z minimal size: $zLength" }
@ -44,7 +71,36 @@ data class EllipsoidPlacement(
random: RandomSource,
position: BlockPos
): Stream<BlockPos> {
return getEllipsoidPositions(random, position)
val count = count.sample(random)
if (count <= 0)
return Stream.empty()
val xLength = xLength.sample(random)
val zLength = zLength.sample(random)
val yLength = yLength.sample(random)
val xPow = xLength * xLength
val zPow = zLength * zLength
val yPow = yLength * yLength
return Stream.generate {
val x = this.x.sample(random) * xLength
val y = this.y.sample(random) * yLength
val z = this.z.sample(random) * zLength
BlockPos(x.toInt(), y.toInt(), z.toInt())
}
.limit(count.toLong() * 10)
.filter {
val (ellipsoidX, ellipsoidY, ellipsoidZ) = it
(ellipsoidX * ellipsoidX) / xPow +
(ellipsoidY * ellipsoidY) / yPow +
(ellipsoidZ * ellipsoidZ) / zPow <= 1.0f
}
.distinct()
.limit(count.toLong())
.map { it + position }
}
override fun type(): PlacementModifierType<*> {

View File

@ -1,60 +0,0 @@
package ru.dbotthepony.mc.otm.worldgen.placement
import com.mojang.serialization.MapCodec
import com.mojang.serialization.codecs.RecordCodecBuilder
import net.minecraft.core.BlockPos
import net.minecraft.util.RandomSource
import net.minecraft.util.valueproviders.FloatProvider
import net.minecraft.util.valueproviders.IntProvider
import net.minecraft.world.level.levelgen.placement.PlacementModifierType
import ru.dbotthepony.mc.otm.registry.data.MPlacementModifiers
import java.util.stream.Stream
/**
* Enormous ellipsoid ("cloud") placement, suitable to be used as ONLY primary placement modifier
*
* This placement modifier is designed to be terminal; other placement modifiers after this MUST NOT be placed
*
* @see AbstractEnormousPlacement
*/
class EnormousEllipsoidPlacement(
parameters: Parameters,
override val x: FloatProvider,
override val z: FloatProvider,
override val y: FloatProvider,
override val count: IntProvider,
override val xLength: FloatProvider,
override val zLength: FloatProvider,
override val yLength: FloatProvider,
) : AbstractEnormousPlacement(parameters), IEllipsoidPlacement {
init {
require(xLength.minValue >= 1f) { "Bad ellipsoid x minimal size: $xLength" }
require(zLength.minValue >= 1f) { "Bad ellipsoid z minimal size: $zLength" }
require(yLength.minValue >= 1f) { "Bad ellipsoid y minimal size: $yLength" }
}
override fun type(): PlacementModifierType<*> {
return MPlacementModifiers.ENORMOUS_ELLIPSOID_PLACEMENT
}
override fun getPositions(center: BlockPos, random: RandomSource): Stream<BlockPos> {
return getEllipsoidPositions(random, center)
}
companion object {
val CODEC: MapCodec<EnormousEllipsoidPlacement> by lazy {
RecordCodecBuilder.mapCodec {
it.group(
PARAMETERS_CODEC.forGetter(EnormousEllipsoidPlacement::parameters),
FloatProvider.CODEC.fieldOf("x").forGetter(EnormousEllipsoidPlacement::x),
FloatProvider.CODEC.fieldOf("y").forGetter(EnormousEllipsoidPlacement::y),
FloatProvider.CODEC.fieldOf("z").forGetter(EnormousEllipsoidPlacement::z),
IntProvider.CODEC.fieldOf("count").forGetter(EnormousEllipsoidPlacement::count),
FloatProvider.codec(1f, Float.MAX_VALUE).fieldOf("x_size").forGetter(EnormousEllipsoidPlacement::xLength),
FloatProvider.codec(1f, Float.MAX_VALUE).fieldOf("z_size").forGetter(EnormousEllipsoidPlacement::zLength),
FloatProvider.codec(1f, Float.MAX_VALUE).fieldOf("y_size").forGetter(EnormousEllipsoidPlacement::yLength),
).apply(it, ::EnormousEllipsoidPlacement)
}
}
}
}

View File

@ -12,17 +12,16 @@ import net.minecraft.core.BlockPos
import net.minecraft.core.SectionPos
import net.minecraft.util.RandomSource
import net.minecraft.world.level.ChunkPos
import net.minecraft.world.level.Level
import net.minecraft.world.level.WorldGenLevel
import net.minecraft.world.level.levelgen.placement.PlacementContext
import net.minecraft.world.level.levelgen.placement.PlacementModifier
import net.minecraft.world.level.levelgen.placement.PlacementModifierType
import ru.dbotthepony.kommons.util.XXHash64
import ru.dbotthepony.mc.otm.core.util.PCG32RandomSource
import ru.dbotthepony.mc.otm.data.codec.minRange
import ru.dbotthepony.mc.otm.worldgen.placement.AbstractEnormousPlacement.Parameters
import ru.dbotthepony.mc.otm.registry.data.MPlacementModifiers
import java.io.DataOutputStream
import java.time.Duration
import java.util.Collections
import java.util.WeakHashMap
import java.util.concurrent.locks.ReentrantLock
import java.util.stream.Stream
@ -32,35 +31,23 @@ import kotlin.math.sqrt
/**
* Enormous placement base, which allows it to span over several chunks without issues.
*
* MUST come as first placement modifier, other placement modifiers (such as rarity and
* shuffle of center point within chunks) must be provided inside [Parameters.prePlacementModifiers] list, in same order as if they were
* *before* this placement
*
* If "post-processing" placements are required, better provide them as [Parameters.postPlacementModifiers].
* This modifier is designed to be the only "top-level" placement modifier,
* and all logic regarding actual placements embedded in [children]
*/
abstract class AbstractEnormousPlacement(val parameters: Parameters) : PlacementModifier() {
data class Parameters(
/**
* How many chunks away to look for actual placements
*
* Too small value will cause placement cutoffs
*/
val chunkScanRange: Int,
val seedMix: Long,
/**
* Baseline placement modifiers, dictating how to appear in chunk
*/
val prePlacementModifiers: List<PlacementModifier> = listOf(),
/**
* Post placement modifiers, operating on positions returned by this placement
*
* Generally, using this will yield better results
*/
val postPlacementModifiers: List<PlacementModifier> = listOf(),
)
class EnormousPlacement(
/**
* How many chunks away to look for actual placements
*
* Too small value will cause placement cutoffs
*/
val chunkScanRange: Int,
val seedMix: Long,
/**
* Baseline placement modifiers, dictating how to appear in chunk
*/
val children: List<PlacementModifier>,
) : PlacementModifier() {
private class GeneratedChunk(positions: Stream<BlockPos>) {
// TODO: memory inefficient
private val positions = ArrayList<BlockPos>()
@ -78,8 +65,6 @@ abstract class AbstractEnormousPlacement(val parameters: Parameters) : Placement
}
}
protected abstract fun getPositions(center: BlockPos, random: RandomSource): Stream<BlockPos>
private val level2cache = WeakHashMap<WorldGenLevel, Cache<ChunkPos, GeneratedChunk>>()
private val lock = ReentrantLock()
@ -102,7 +87,7 @@ abstract class AbstractEnormousPlacement(val parameters: Parameters) : Placement
val dataStream = DataOutputStream(bytes)
dataStream.writeLong(context.level.seed)
dataStream.writeLong(parameters.seedMix)
dataStream.writeLong(seedMix)
dataStream.writeInt(pos.x)
dataStream.writeInt(pos.z)
@ -111,25 +96,24 @@ abstract class AbstractEnormousPlacement(val parameters: Parameters) : Placement
val random = PCG32RandomSource(hash.digestAsLong())
var stream = Stream.of(BlockPos(pos.minBlockX, 0, pos.minBlockZ))
parameters.prePlacementModifiers.forEach { modifier -> stream = stream.flatMap { modifier.getPositions(context, random, it).sequential() } }
stream = stream.flatMap { getPositions(it, random) }
parameters.postPlacementModifiers.forEach { modifier -> stream = stream.flatMap { modifier.getPositions(context, random, it).sequential() } }
children.forEach { modifier -> stream = stream.flatMap { modifier.getPositions(context, random, it).sequential() } }
return GeneratedChunk(stream)
}
final override fun getPositions(context: PlacementContext, random: RandomSource, pos: BlockPos): Stream<BlockPos> {
override fun getPositions(context: PlacementContext, random: RandomSource, pos: BlockPos): Stream<BlockPos> {
val cache = getCache(context.level)
val cPos = ChunkPos(pos)
val instances = ArrayList<GeneratedChunk>()
for (x in -parameters.chunkScanRange .. parameters.chunkScanRange) {
for (z in -parameters.chunkScanRange .. parameters.chunkScanRange) {
for (x in -chunkScanRange .. chunkScanRange) {
for (z in -chunkScanRange .. chunkScanRange) {
// floor, so chunk scan range of 1 will give square instead of diamond
val radius = sqrt(x.toDouble() * x + z.toDouble() * z).toInt()
if (radius <= parameters.chunkScanRange) {
if (radius <= chunkScanRange) {
val thisPos = ChunkPos(cPos.x + x, cPos.z + z)
instances.add(getCache(context.level).get(thisPos) { computeChunk(context, thisPos) })
instances.add(cache.get(thisPos) { computeChunk(context, thisPos) })
}
}
}
@ -137,14 +121,17 @@ abstract class AbstractEnormousPlacement(val parameters: Parameters) : Placement
return instances.stream().flatMap { it.getPositions(cPos) }
}
override fun type(): PlacementModifierType<*> {
return MPlacementModifiers.ENORMOUS
}
companion object {
val PARAMETERS_CODEC: MapCodec<Parameters> = RecordCodecBuilder.mapCodec {
val CODEC: MapCodec<EnormousPlacement> = RecordCodecBuilder.mapCodec {
it.group(
Codec.INT.minRange(0).fieldOf("chunk_scan_range").forGetter(Parameters::chunkScanRange),
Codec.LONG.fieldOf("seed_mix").forGetter(Parameters::seedMix),
CODEC.listOf().optionalFieldOf("placement", listOf()).forGetter(Parameters::prePlacementModifiers),
CODEC.listOf().optionalFieldOf("post_placement", listOf()).forGetter(Parameters::postPlacementModifiers),
).apply(it, ::Parameters)
Codec.INT.minRange(0).fieldOf("chunk_scan_range").forGetter(EnormousPlacement::chunkScanRange),
Codec.LONG.fieldOf("seed_mix").forGetter(EnormousPlacement::seedMix),
PlacementModifier.CODEC.listOf().optionalFieldOf("children", listOf()).forGetter(EnormousPlacement::children),
).apply(it, ::EnormousPlacement)
}
}
}

View File

@ -1,81 +0,0 @@
package ru.dbotthepony.mc.otm.worldgen.placement
import net.minecraft.core.BlockPos
import net.minecraft.util.RandomSource
import net.minecraft.util.valueproviders.FloatProvider
import net.minecraft.util.valueproviders.IntProvider
import ru.dbotthepony.mc.otm.core.math.component1
import ru.dbotthepony.mc.otm.core.math.component2
import ru.dbotthepony.mc.otm.core.math.component3
import ru.dbotthepony.mc.otm.core.math.plus
import java.util.stream.Stream
interface IEllipsoidPlacement {
/**
* X position within ellipsoid sampler, expected to be in range -1 .. 1
*/
val x: FloatProvider
/**
* Z position within ellipsoid sampler, expected to be in range -1 .. 1
*/
val z: FloatProvider
/**
* Y position within ellipsoid sampler, expected to be in range -1 .. 1
*/
val y: FloatProvider
/**
* Total amount of block positions to generate
*/
val count: IntProvider
/**
* Ellipsoid size sampler on X axis
*/
val xLength: FloatProvider
/**
* Ellipsoid size sampler on Z axis
*/
val zLength: FloatProvider
/**
* Ellipsoid size sampler on Y axis
*/
val yLength: FloatProvider
fun getEllipsoidPositions(random: RandomSource, position: BlockPos): Stream<BlockPos> {
val count = count.sample(random)
if (count <= 0)
return Stream.empty()
val xLength = xLength.sample(random)
val zLength = zLength.sample(random)
val yLength = yLength.sample(random)
val xPow = xLength * xLength
val zPow = zLength * zLength
val yPow = yLength * yLength
return Stream.generate {
val x = this.x.sample(random) * xLength
val y = this.y.sample(random) * yLength
val z = this.z.sample(random) * zLength
BlockPos(x.toInt(), y.toInt(), z.toInt())
}
.limit(count.toLong() * 10)
.filter {
val (ellipsoidX, ellipsoidY, ellipsoidZ) = it
(ellipsoidX * ellipsoidX) / xPow +
(ellipsoidY * ellipsoidY) / yPow +
(ellipsoidZ * ellipsoidZ) / zPow <= 1.0f
}
.distinct()
.limit(count.toLong())
.map { it + position }
}
}

View File

@ -10,6 +10,8 @@ import net.minecraft.util.valueproviders.ConstantFloat
import net.minecraft.util.valueproviders.FloatProvider
import net.minecraft.util.valueproviders.IntProvider
import net.minecraft.util.valueproviders.UniformFloat
import net.minecraft.world.level.levelgen.placement.PlacementContext
import net.minecraft.world.level.levelgen.placement.PlacementModifier
import net.minecraft.world.level.levelgen.placement.PlacementModifierType
import ru.dbotthepony.mc.otm.core.math.Vector
import ru.dbotthepony.mc.otm.core.math.angleDifference
@ -28,7 +30,6 @@ import kotlin.math.sign
import kotlin.math.sin
class WormPlacement(
parameters: Parameters,
val length: IntProvider,
val turnChanceXZ: BooleanProvider,
val turnChanceXY: BooleanProvider,
@ -39,7 +40,7 @@ class WormPlacement(
val initialAngleXY: FloatProvider = DEFAULT_INITIAL_ANGLE_XY,
val maxTravelDown: Int = Int.MAX_VALUE,
val maxTravelUp: Int = Int.MAX_VALUE,
) : AbstractEnormousPlacement(parameters) {
) : PlacementModifier() {
private inner class Worm(private val random: RandomSource, private var position: Vector) {
private var remainingDistance = length.sample(random)
private var xzRotation = random.nextDouble(-PI / 2.0, PI / 2.0)
@ -129,7 +130,7 @@ class WormPlacement(
}
}
override fun getPositions(center: BlockPos, random: RandomSource): Stream<BlockPos> {
override fun getPositions(context: PlacementContext, random: RandomSource, center: BlockPos): Stream<BlockPos> {
val worms = ArrayList<Worm>()
worms.add(Worm(random, Vector.ZERO))
val positions = ArrayList<BlockPos>()
@ -200,7 +201,6 @@ class WormPlacement(
val CODEC: MapCodec<WormPlacement> = RecordCodecBuilder.mapCodec {
it.group(
PARAMETERS_CODEC.forGetter(WormPlacement::parameters),
IntProvider.CODEC.fieldOf("length").forGetter(WormPlacement::length),
BooleanProvider.CODEC.fieldOf("turn_chance_xz").forGetter(WormPlacement::turnChanceXZ),
BooleanProvider.CODEC.fieldOf("turn_chance_xy").forGetter(WormPlacement::turnChanceXY),