Enormous placement modifier, greatly enhance dilithium ore placement

This commit is contained in:
DBotThePony 2025-03-01 22:48:01 +07:00
parent f655405d4b
commit a903b9cff8
Signed by: DBot
GPG Key ID: DCC23B5715498507
11 changed files with 306 additions and 86 deletions

View File

@ -13,6 +13,7 @@ val mod_id: String by project
val handle_deps: String by project
val use_commit_hash_in_version: String by project
val handleDeps = handle_deps.toBoolean()
val caffeine_cache_version: String by project
plugins {
java
@ -142,14 +143,11 @@ dependencies {
implementation("thedarkcolour:kotlinforforge-neoforge:$kotlin_for_forge_version")
jarJar("ru.dbotthepony.kommons:kommons:[$kommons_version,)") { setTransitive(false) }
implementation("ru.dbotthepony.kommons:kommons:[$kommons_version,)") { setTransitive(false) }
jarJar(implementation("com.github.ben-manes.caffeine:caffeine:[$caffeine_cache_version,)")!!)
jarJar("ru.dbotthepony.kommons:kommons-gson:[$kommons_version,)") { setTransitive(false) }
implementation("ru.dbotthepony.kommons:kommons-gson:[$kommons_version,)") { setTransitive(false) }
jarJar("ru.dbotthepony.kommons:kommons-guava:[$kommons_version,)") { setTransitive(false) }
implementation("ru.dbotthepony.kommons:kommons-guava:[$kommons_version,)") { setTransitive(false) }
jarJar(implementation("ru.dbotthepony.kommons:kommons:[$kommons_version,)") { setTransitive(false) })
jarJar(implementation("ru.dbotthepony.kommons:kommons-gson:[$kommons_version,)") { setTransitive(false) })
jarJar(implementation("ru.dbotthepony.kommons:kommons-guava:[$kommons_version,)") { setTransitive(false) })
compileOnly("yalter.mousetweaks:MouseTweaks:2.23:api")
annotationProcessor("org.spongepowered:mixin:${mixin_version}:processor")
@ -228,6 +226,10 @@ minecraft {
// Log4j console level
systemProperty("forge.logging.console.level", "debug")
dependencies {
runtime("com.github.ben-manes.caffeine:caffeine:[$caffeine_cache_version,)")
}
}
getByName("client") {

View File

@ -23,6 +23,7 @@ neogradle.subsystems.parchment.minecraftVersion=1.21.1
neogradle.subsystems.parchment.mappingsVersion=2024.11.17
kommons_version=3.1.3
caffeine_cache_version=3.1.5
jei_version=19.16.4.171
jupiter_version=5.9.2

View File

@ -9,6 +9,7 @@ import net.minecraft.tags.BlockTags
import net.minecraft.util.valueproviders.ClampedNormalFloat
import net.minecraft.util.valueproviders.ClampedNormalInt
import net.minecraft.util.valueproviders.ConstantFloat
import net.minecraft.util.valueproviders.UniformFloat
import net.minecraft.util.valueproviders.UniformInt
import net.minecraft.world.level.levelgen.GenerationStep
import net.minecraft.world.level.levelgen.VerticalAnchor
@ -25,11 +26,12 @@ import net.minecraft.world.level.levelgen.structure.templatesystem.TagMatchTest
import net.neoforged.neoforge.common.world.BiomeModifier
import net.neoforged.neoforge.registries.NeoForgeRegistries
import ru.dbotthepony.mc.otm.core.math.Decimal
import ru.dbotthepony.mc.otm.data.world.EllipsoidPlacement
import ru.dbotthepony.mc.otm.data.world.StandardDeviationHeightProvider
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.placement.AbstractEnormousPlacement
import ru.dbotthepony.mc.otm.worldgen.placement.EnormousEllipsoidPlacement
private object ConfiguredFeatures {
val TRITANIUM_ORE = key("tritanium_ore")
@ -71,7 +73,6 @@ fun registerConfiguredFeatures(context: BootstrapContext<ConfiguredFeature<*, *>
private object PlacedFeatures {
val NORMAL_TRITANIUM = key("normal_tritanium")
val DEEP_TRITANIUM = key("deep_tritanium")
val CLOUD_TITANIUM = key("cloud_tritanium")
val DILITHIUM = key("dilithium")
val BLACK_HOLE = key("black_hole")
@ -103,24 +104,6 @@ fun registerPlacedFeatures(context: BootstrapContext<PlacedFeature>) {
HeightRangePlacement.of(VeryBiasedToBottomHeight.of(VerticalAnchor.aboveBottom(4), VerticalAnchor.absolute(0), 16))
)
))
context.register(PlacedFeatures.CLOUD_TITANIUM, PlacedFeature(
ore,
listOf(
RarityFilter.onAverageOnceEvery(16),
InSquarePlacement.spread(),
HeightRangePlacement.of(StandardDeviationHeightProvider(VerticalAnchor.absolute(10), 15.0)),
EllipsoidPlacement(
x = ClampedNormalInt.of(0f, 6f, Int.MIN_VALUE, Int.MAX_VALUE),
y = ClampedNormalInt.of(0f, 12f, Int.MIN_VALUE, Int.MAX_VALUE),
z = ClampedNormalInt.of(0f, 6f, Int.MIN_VALUE, Int.MAX_VALUE),
count = ClampedNormalInt.of(60f, 60f, 40, 160),
xLength = ClampedNormalFloat.of(11f, 4f, 6f, 14f),
yLength = ClampedNormalFloat.of(11f, 4f, 6f, 14f),
zLength = ClampedNormalFloat.of(11f, 4f, 6f, 14f),
)
)
))
}
run {
@ -129,19 +112,22 @@ fun registerPlacedFeatures(context: BootstrapContext<PlacedFeature>) {
context.register(PlacedFeatures.DILITHIUM, PlacedFeature(
ore,
listOf(
RarityFilter.onAverageOnceEvery(12),
InSquarePlacement.spread(),
HeightRangePlacement.of(StandardDeviationHeightProvider(VerticalAnchor.absolute(0), 15.0)),
EllipsoidPlacement(
x = ClampedNormalInt.of(0f, 8f, Int.MIN_VALUE, Int.MAX_VALUE),
y = ClampedNormalInt.of(0f, 20f, Int.MIN_VALUE, Int.MAX_VALUE),
z = ClampedNormalInt.of(0f, 8f, Int.MIN_VALUE, Int.MAX_VALUE),
count = ClampedNormalInt.of(200f, 200f, 200, 600),
xLength = ClampedNormalFloat.of(11f, 4f, 8f, 14f),
// allow crystals to generate as far as standard deviation allows
// to increase chance for player to discover crystal vein
yLength = ConstantFloat.of(60f),
zLength = ClampedNormalFloat.of(11f, 4f, 8f, 14f),
EnormousEllipsoidPlacement(
parameters = AbstractEnormousPlacement.Parameters(
chunkScanRange = 4,
placementModifiers = listOf(
RarityFilter.onAverageOnceEvery(40),
InSquarePlacement.spread(),
HeightRangePlacement.of(StandardDeviationHeightProvider(VerticalAnchor.absolute(0), 15.0)),
)
),
x = ClampedNormalFloat.of(0f, 0.5f, 0f, 2f),
y = ClampedNormalFloat.of(0f, 0.5f, 0f, 2f),
z = ClampedNormalFloat.of(0f, 0.5f, 0f, 2f),
count = UniformInt.of(4800, 12400),
xLength = UniformFloat.of(60f, 80f),
yLength = UniformFloat.of(60f, 80f),
zLength = UniformFloat.of(60f, 80f),
)
)
))
@ -179,7 +165,6 @@ fun registerBiomeModifiers(context: BootstrapContext<BiomeModifier>) {
HolderSet.direct(
placed.getOrThrow(PlacedFeatures.NORMAL_TRITANIUM),
placed.getOrThrow(PlacedFeatures.DEEP_TRITANIUM),
placed.getOrThrow(PlacedFeatures.CLOUD_TITANIUM),
placed.getOrThrow(PlacedFeatures.DILITHIUM),
),
GenerationStep.Decoration.UNDERGROUND_ORES

View File

@ -53,3 +53,5 @@ fun <S : Comparable<S>> Codec<S>.maxRange(max: S, exclusive: Boolean = false) =
ComparableCodec(this, max = max, maxExclusive = exclusive)
fun <S : Comparable<S>> Codec<S>.inRange(min: S, minExclusive: Boolean = false, max: S, maxExclusive: Boolean = false) =
ComparableCodec(this, min, max, minExclusive, maxExclusive)
fun <S : Comparable<S>> Codec<S>.inRange(min: S, max: S) =
ComparableCodec(this, min, max, false, false)

View File

@ -3,7 +3,7 @@ package ru.dbotthepony.mc.otm.registry.data
import net.minecraft.core.registries.BuiltInRegistries
import net.minecraft.world.level.levelgen.heightproviders.HeightProviderType
import net.neoforged.bus.api.IEventBus
import ru.dbotthepony.mc.otm.data.world.StandardDeviationHeightProvider
import ru.dbotthepony.mc.otm.worldgen.placement.StandardDeviationHeightProvider
import ru.dbotthepony.mc.otm.registry.MDeferredRegister
object MHeightProviders {

View File

@ -3,8 +3,9 @@ package ru.dbotthepony.mc.otm.registry.data
import net.minecraft.core.registries.BuiltInRegistries
import net.minecraft.world.level.levelgen.placement.PlacementModifierType
import net.neoforged.bus.api.IEventBus
import ru.dbotthepony.mc.otm.data.world.EllipsoidPlacement
import ru.dbotthepony.mc.otm.registry.MDeferredRegister
import ru.dbotthepony.mc.otm.worldgen.placement.EllipsoidPlacement
import ru.dbotthepony.mc.otm.worldgen.placement.EnormousEllipsoidPlacement
object MPlacementModifiers {
private val registry = MDeferredRegister(BuiltInRegistries.PLACEMENT_MODIFIER_TYPE)
@ -14,4 +15,5 @@ object MPlacementModifiers {
}
val ELLIPSOID_PLACEMENT by registry.register("ellipsoid") { PlacementModifierType { EllipsoidPlacement.CODEC } }
val ENORMOUS_ELLIPSOID_PLACEMENT by registry.register("enormous_ellipsoid") { PlacementModifierType { EnormousEllipsoidPlacement.CODEC } }
}

View File

@ -0,0 +1,110 @@
package ru.dbotthepony.mc.otm.worldgen.placement
import com.github.benmanes.caffeine.cache.Caffeine
import com.github.benmanes.caffeine.cache.Scheduler
import com.mojang.serialization.Codec
import com.mojang.serialization.MapCodec
import com.mojang.serialization.codecs.RecordCodecBuilder
import it.unimi.dsi.fastutil.HashCommon
import net.minecraft.Util
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.levelgen.placement.PlacementContext
import net.minecraft.world.level.levelgen.placement.PlacementModifier
import ru.dbotthepony.mc.otm.data.codec.inRange
import ru.dbotthepony.mc.otm.data.codec.minRange
import java.time.Duration
import java.util.stream.Stream
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.placementModifiers] list, in same order as if they were
* *before* this placement
*/
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,
/**
* Baseline placement modifiers, dictating how to appear in chunk
*/
val placementModifiers: List<PlacementModifier>,
/**
* How many chunks to remember placements in, usually not required to be adjusted
*/
val chunkCacheSize: Int = 16384,
)
private class GeneratedChunk(positions: Stream<BlockPos>) {
private val positions = ArrayList<BlockPos>()
init {
positions.forEach { this.positions.add(it) }
}
fun getPositions(pos: ChunkPos): Stream<BlockPos> {
return positions.stream().filter {
SectionPos.blockToSectionCoord(it.x) == pos.x &&
SectionPos.blockToSectionCoord(it.z) == pos.z
}
}
}
protected abstract fun getPositions(center: BlockPos, random: RandomSource): Stream<BlockPos>
private val chunkCache = Caffeine.newBuilder()
.scheduler(Scheduler.systemScheduler())
.executor(Util.backgroundExecutor())
.maximumSize(parameters.chunkCacheSize.toLong())
.expireAfterWrite(Duration.ofMinutes(5))
.softValues()
.build<ChunkPos, GeneratedChunk>()
private fun computeChunk(context: PlacementContext, pos: ChunkPos): GeneratedChunk {
val random = RandomSource.create(context.level.seed + HashCommon.murmurHash3(pos.x + pos.z * 31))
var stream = Stream.of(BlockPos(pos.minBlockX, 0, pos.minBlockZ))
parameters.placementModifiers.forEach { modifier -> stream = stream.flatMap { modifier.getPositions(context, random, it).sequential() } }
return GeneratedChunk(stream.flatMap { getPositions(it, random) })
}
final override fun getPositions(context: PlacementContext, random: RandomSource, pos: BlockPos): Stream<BlockPos> {
val cPos = ChunkPos(pos)
val instances = ArrayList<GeneratedChunk>()
for (x in -parameters.chunkScanRange .. parameters.chunkScanRange) {
for (z in -parameters.chunkScanRange .. parameters.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) {
val thisPos = ChunkPos(cPos.x + x, cPos.z + z)
instances.add(chunkCache.get(thisPos) { computeChunk(context, thisPos) })
}
}
}
return instances.stream().flatMap { it.getPositions(cPos) }
}
companion object {
val PARAMETERS_CODEC: MapCodec<Parameters> = RecordCodecBuilder.mapCodec {
it.group(
Codec.INT.minRange(0).fieldOf("chunk_scan_range").forGetter(Parameters::chunkScanRange),
CODEC.listOf().fieldOf("placement").forGetter(Parameters::placementModifiers),
Codec.INT.inRange(0, 128_000).optionalFieldOf("cache_size", 16384).forGetter(Parameters::chunkCacheSize),
).apply(it, ::Parameters)
}
}
}

View File

@ -1,4 +1,4 @@
package ru.dbotthepony.mc.otm.data.world
package ru.dbotthepony.mc.otm.worldgen.placement
import com.mojang.serialization.MapCodec
import com.mojang.serialization.codecs.RecordCodecBuilder
@ -19,16 +19,20 @@ import java.util.stream.Stream
import kotlin.math.PI
import kotlin.math.roundToInt
// aka "cloud placement"
/**
* Regular ellipsoid ("cloud") placement, suitable to be used as non-primary placement modifier
*
* This placement modifier is designed to be terminal; other placement modifiers after this MUST NOT be placed
*/
data class EllipsoidPlacement(
val x: IntProvider,
val z: IntProvider,
val y: IntProvider,
val count: IntProvider,
val xLength: FloatProvider,
val zLength: FloatProvider,
val yLength: FloatProvider,
) : PlacementModifier() {
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 {
init {
require(xLength.minValue >= 1f) { "Bad ellipsoid x minimal size: $xLength" }
require(zLength.minValue >= 1f) { "Bad ellipsoid z minimal size: $zLength" }
@ -40,34 +44,7 @@ data class EllipsoidPlacement(
random: RandomSource,
position: BlockPos
): Stream<BlockPos> {
var 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
count = minOf(count, (xLength * zLength * yLength * PI * (4.0 / 3.0)).roundToInt())
count = 600
return Stream.generate { position + BlockPos(this.x.sample(random), this.y.sample(random), this.z.sample(random)) }
.limit(count * 10L) // failsafe
.filter {
val (ellipsoidX, ellipsoidY, ellipsoidZ) = it - position
(ellipsoidX * ellipsoidX) / xPow +
(ellipsoidY * ellipsoidY) / yPow +
(ellipsoidZ * ellipsoidZ) / zPow <= 1.0f
}
.distinct()
.limit(count.toLong())
return getEllipsoidPositions(random, position)
}
override fun type(): PlacementModifierType<*> {
@ -78,9 +55,9 @@ data class EllipsoidPlacement(
val CODEC: MapCodec<EllipsoidPlacement> by lazy {
RecordCodecBuilder.mapCodec {
it.group(
IntProvider.CODEC.fieldOf("x").forGetter(EllipsoidPlacement::x),
IntProvider.CODEC.fieldOf("y").forGetter(EllipsoidPlacement::y),
IntProvider.CODEC.fieldOf("z").forGetter(EllipsoidPlacement::z),
FloatProvider.CODEC.fieldOf("x").forGetter(EllipsoidPlacement::x),
FloatProvider.CODEC.fieldOf("y").forGetter(EllipsoidPlacement::y),
FloatProvider.CODEC.fieldOf("z").forGetter(EllipsoidPlacement::z),
IntProvider.CODEC.fieldOf("count").forGetter(EllipsoidPlacement::count),
FloatProvider.codec(1f, Float.MAX_VALUE).fieldOf("x_size").forGetter(EllipsoidPlacement::xLength),
FloatProvider.codec(1f, Float.MAX_VALUE).fieldOf("z_size").forGetter(EllipsoidPlacement::zLength),

View File

@ -0,0 +1,60 @@
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

@ -0,0 +1,81 @@
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

@ -1,4 +1,4 @@
package ru.dbotthepony.mc.otm.data.world
package ru.dbotthepony.mc.otm.worldgen.placement
import com.mojang.serialization.Codec
import com.mojang.serialization.MapCodec