Enhanced placements prototyping

This commit is contained in:
DBotThePony 2025-03-24 18:42:52 +07:00
parent 03a1ab9197
commit 18f9bc2654
Signed by: DBot
GPG Key ID: DCC23B5715498507
11 changed files with 579 additions and 11 deletions

View File

@ -57,6 +57,7 @@ import ru.dbotthepony.mc.otm.datagen.tags.addStructureTags
import ru.dbotthepony.mc.otm.datagen.tags.addSuspiciousTags
import ru.dbotthepony.mc.otm.datagen.tags.addTags
import ru.dbotthepony.mc.otm.matter.MatterDataProvider
import ru.dbotthepony.mc.otm.registry.MRegistries
import ru.dbotthepony.mc.otm.registry.game.MBlocks
import ru.dbotthepony.mc.otm.registry.objects.ColoredDecorativeBlock
import ru.dbotthepony.mc.otm.registry.objects.DecorativeBlock
@ -560,6 +561,7 @@ object DataGen {
val registrySetBuilder = RegistrySetBuilder()
.add(Registries.DAMAGE_TYPE, ::registerDamageTypes)
.add(Registries.CONFIGURED_FEATURE, ::registerConfiguredFeatures)
.add(MRegistries.CONFIGURED_FEATURE, ::registerEnhancedConfiguredFeatures)
.add(Registries.PLACED_FEATURE, ::registerPlacedFeatures)
.add(NeoForgeRegistries.Keys.BIOME_MODIFIERS, ::registerBiomeModifiers)

View File

@ -28,11 +28,14 @@ import net.neoforged.neoforge.registries.NeoForgeRegistries
import ru.dbotthepony.mc.otm.core.math.Decimal
import ru.dbotthepony.mc.otm.data.world.BooleanProvider
import ru.dbotthepony.mc.otm.data.world.OneOfFloatProvider
import ru.dbotthepony.mc.otm.registry.MRegistries
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.EnhancedPlacedFeature
import ru.dbotthepony.mc.otm.worldgen.feature.BlackHolePlacerFeature
import ru.dbotthepony.mc.otm.worldgen.feature.DebugPlacerFeature
import ru.dbotthepony.mc.otm.worldgen.feature.EnhancedFeature
import ru.dbotthepony.mc.otm.worldgen.placement.ChainPlacement
import ru.dbotthepony.mc.otm.worldgen.placement.EnormousPlacement
import ru.dbotthepony.mc.otm.worldgen.placement.EllipsoidPlacement
@ -42,12 +45,30 @@ import ru.dbotthepony.mc.otm.worldgen.placement.WormPlacement
private object ConfiguredFeatures {
val TRITANIUM_ORE = key("tritanium_ore")
val TRITANIUM_ORE_SMALL = key("tritanium_ore_small")
val DILITHIUM = key("dilithium")
val DILITHIUM = ekey("dilithium")
val BLACK_HOLE = key("black_hole")
private fun key(name: String): ResourceKey<ConfiguredFeature<*, *>> {
return ResourceKey.create(Registries.CONFIGURED_FEATURE, modLocation(name))
}
private fun ekey(name: String): ResourceKey<EnhancedFeature.Configured<*, *>> {
return ResourceKey.create(MRegistries.CONFIGURED_FEATURE, modLocation(name))
}
}
fun registerEnhancedConfiguredFeatures(context: BootstrapContext<EnhancedFeature.Configured<*, *>>) {
val stone = TagMatchTest(BlockTags.STONE_ORE_REPLACEABLES)
val deepslate = TagMatchTest(BlockTags.DEEPSLATE_ORE_REPLACEABLES)
run {
val target = listOf(
OreConfiguration.target(stone, MBlocks.DILITHIUM_ORE.defaultBlockState()),
OreConfiguration.target(deepslate, MBlocks.DEEPSLATE_DILITHIUM_ORE.defaultBlockState()),
)
context.register(ConfiguredFeatures.DILITHIUM, EnhancedFeature.Wrapper.configure(ConfiguredFeature(Feature.REPLACE_SINGLE_BLOCK, ReplaceBlockConfiguration(target))))
}
}
fun registerConfiguredFeatures(context: BootstrapContext<ConfiguredFeature<*, *>>) {
@ -65,15 +86,6 @@ fun registerConfiguredFeatures(context: BootstrapContext<ConfiguredFeature<*, *>
//context.register(ConfiguredFeatures.TRITANIUM_ORE_SMALL, ConfiguredFeature(MWorldGenFeatures.DEBUG_PLACEMENT, DebugPlacerFeature.Config(MBlocks.TRITANIUM_ORE.defaultBlockState())))
}
run {
val target = listOf(
OreConfiguration.target(stone, MBlocks.DILITHIUM_ORE.defaultBlockState()),
OreConfiguration.target(deepslate, MBlocks.DEEPSLATE_DILITHIUM_ORE.defaultBlockState()),
)
context.register(ConfiguredFeatures.DILITHIUM, ConfiguredFeature(Feature.REPLACE_SINGLE_BLOCK, ReplaceBlockConfiguration(target)))
}
context.register(ConfiguredFeatures.BLACK_HOLE, ConfiguredFeature(
MWorldGenFeatures.BLACK_HOLE_PLACER,
BlackHolePlacerFeature.Config(Decimal("0.25"), Decimal(1))))
@ -93,6 +105,7 @@ private object PlacedFeatures {
fun registerPlacedFeatures(context: BootstrapContext<PlacedFeature>) {
val configured = context.lookup(Registries.CONFIGURED_FEATURE)
val econfigured = context.lookup(MRegistries.CONFIGURED_FEATURE)
run {
val ore = configured.getOrThrow(ConfiguredFeatures.TRITANIUM_ORE)
@ -169,13 +182,14 @@ fun registerPlacedFeatures(context: BootstrapContext<PlacedFeature>) {
}
run {
val ore = configured.getOrThrow(ConfiguredFeatures.DILITHIUM)
val ore = econfigured.getOrThrow(ConfiguredFeatures.DILITHIUM)
val ringularity = OneOfFloatProvider.of(
ClampedNormalFloat.of(0.4f, 0.2f, -2f, 2f),
ClampedNormalFloat.of(-0.4f, 0.2f, -2f, 2f),
)
/*
context.register(PlacedFeatures.DILITHIUM, PlacedFeature(
ore,
listOf(
@ -199,6 +213,30 @@ fun registerPlacedFeatures(context: BootstrapContext<PlacedFeature>) {
)
)
))
*/
context.register(
PlacedFeatures.DILITHIUM,
EnhancedPlacedFeature.Builder(RarityFilter.onAverageOnceEvery(120))
.then(InSquarePlacement.spread())
.then(HeightRangePlacement.of(StandardDeviationHeightProvider(VerticalAnchor.absolute(0), 15.0)))
.then(
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),
)
)
.then(ore)
.build(
chunkScanRange = 6,
seedMix = 237483209523709234L,
)
)
}
val blackHole = configured.getOrThrow(ConfiguredFeatures.BLACK_HOLE)

View File

@ -92,6 +92,8 @@ import ru.dbotthepony.mc.otm.registry.data.MWorldGenFeatures
import ru.dbotthepony.mc.otm.server.MCommands
import ru.dbotthepony.mc.otm.storage.StorageStack
import ru.dbotthepony.mc.otm.triggers.KillAsAndroidTrigger
import ru.dbotthepony.mc.otm.worldgen.feature.EnhancedFeature
import ru.dbotthepony.mc.otm.worldgen.placement.EnhancedPlacementModifier
import thedarkcolour.kotlinforforge.neoforge.forge.DIST
import thedarkcolour.kotlinforforge.neoforge.forge.FORGE_BUS
import thedarkcolour.kotlinforforge.neoforge.forge.LOADING_CONTEXT
@ -139,6 +141,8 @@ object OverdriveThatMatters {
DecimalProvider.register(MOD_BUS)
BooleanProvider.register(MOD_BUS)
EnhancedFeature.register(MOD_BUS)
EnhancedPlacementModifier.register(MOD_BUS)
AndroidResearchDescriptions.register(MOD_BUS)
AndroidResearchResults.register(MOD_BUS)

View File

@ -55,6 +55,9 @@ object MBuiltInRegistries {
defaultKey(ResourceLocation(OverdriveThatMatters.MOD_ID, "false"))
}
val FEATURE by Delegate(MRegistries.FEATURE)
val PLACEMENT_MODIFIER by Delegate(MRegistries.PLACEMENT_MODIFIER)
internal fun register(bus: IEventBus) {
delegates.forEach { bus.addListener(it::build) }
}

View File

@ -13,6 +13,9 @@ import ru.dbotthepony.mc.otm.player.android.AndroidFeatureType
import ru.dbotthepony.mc.otm.player.android.AndroidResearchDescription
import ru.dbotthepony.mc.otm.player.android.AndroidResearchResult
import ru.dbotthepony.mc.otm.storage.StorageStack
import ru.dbotthepony.mc.otm.worldgen.EnhancedPlacedFeature
import ru.dbotthepony.mc.otm.worldgen.feature.EnhancedFeature
import ru.dbotthepony.mc.otm.worldgen.placement.EnhancedPlacementModifier
object MRegistries {
private fun <T> k(name: String): ResourceKey<Registry<T>> {
@ -28,4 +31,7 @@ object MRegistries {
val ANDROID_FEATURE = k<AndroidFeatureType<*>>("android_feature")
val STACK_TYPE = k<StorageStack.Type<*>>("stack_type")
val BOOLEAN_PROVIDER = k<BooleanProvider.Type<*>>("boolean_provider")
val FEATURE = k<EnhancedFeature<*>>("feature")
val CONFIGURED_FEATURE = k<EnhancedFeature.Configured<*, *>>("configured_feature")
val PLACEMENT_MODIFIER = k<EnhancedPlacementModifier.Type<*>>("placement_modifier")
}

View File

@ -4,6 +4,7 @@ import net.minecraft.core.registries.BuiltInRegistries
import net.neoforged.bus.api.IEventBus
import ru.dbotthepony.kommons.util.getValue
import ru.dbotthepony.mc.otm.registry.MDeferredRegister
import ru.dbotthepony.mc.otm.worldgen.EnhancedPlacedFeature
import ru.dbotthepony.mc.otm.worldgen.feature.BlackHolePlacerFeature
import ru.dbotthepony.mc.otm.worldgen.feature.DebugPlacerFeature
@ -16,4 +17,5 @@ object MWorldGenFeatures {
val BLACK_HOLE_PLACER by registry.register("black_hole_placer") { BlackHolePlacerFeature }
val DEBUG_PLACEMENT by registry.register("debug") { DebugPlacerFeature }
val ENHANCED_FEATURE by registry.register("enhanced_feature") { EnhancedPlacedFeature }
}

View File

@ -0,0 +1,299 @@
package ru.dbotthepony.mc.otm.worldgen
import com.github.benmanes.caffeine.cache.Cache
import com.github.benmanes.caffeine.cache.Caffeine
import com.github.benmanes.caffeine.cache.Scheduler
import com.mojang.datafixers.util.Either
import com.mojang.datafixers.util.Pair
import com.mojang.serialization.Codec
import com.mojang.serialization.DataResult
import com.mojang.serialization.DynamicOps
import com.mojang.serialization.codecs.RecordCodecBuilder
import it.unimi.dsi.fastutil.io.FastByteArrayOutputStream
import net.minecraft.Util
import net.minecraft.core.BlockPos
import net.minecraft.core.Holder
import net.minecraft.core.SectionPos
import net.minecraft.world.level.ChunkPos
import net.minecraft.world.level.WorldGenLevel
import net.minecraft.world.level.levelgen.feature.ConfiguredFeature
import net.minecraft.world.level.levelgen.feature.Feature
import net.minecraft.world.level.levelgen.feature.FeaturePlaceContext
import net.minecraft.world.level.levelgen.feature.configurations.FeatureConfiguration
import net.minecraft.world.level.levelgen.placement.PlacedFeature
import net.minecraft.world.level.levelgen.placement.PlacementModifier
import ru.dbotthepony.kommons.util.XXHash64
import ru.dbotthepony.mc.otm.core.util.GJRAND64RandomSource
import ru.dbotthepony.mc.otm.data.codec.minRange
import ru.dbotthepony.mc.otm.worldgen.feature.EnhancedFeature
import ru.dbotthepony.mc.otm.worldgen.placement.EnhancedPlacementModifier
import java.io.DataOutputStream
import java.time.Duration
import java.util.IdentityHashMap
import kotlin.math.sqrt
private object NodeCodec : Codec<EnhancedPlacedFeature.Node> {
private val list = Codec.list(this)
override fun <T : Any?> encode(input: EnhancedPlacedFeature.Node, ops: DynamicOps<T>, prefix: T): DataResult<T> {
val result = HashMap<T, T>()
if (input.children.isNotEmpty()) {
val re = list.encodeStart(ops, input.children)
if (re.isError)
return re
result[ops.createString("children")] = re.resultOrPartial().get()
}
if (input.contents.left().isPresent) {
val re = EnhancedPlacementModifier.CODEC.encodeStart(ops, input.contents.left().get())
if (re.isError)
return DataResult.error { "Failed to serialize placement modifier: ${re.error().get().message()}" }
result[ops.createString("modifier")] = re.resultOrPartial().get()
} else {
val re = EnhancedFeature.CODEC.encodeStart(ops, input.contents.right().get())
if (re.isError)
return DataResult.error { "Failed to serialize feature: ${re.error().get().message()}" }
result[ops.createString("feature")] = re.resultOrPartial().get()
}
return ops.mergeToMap(prefix, result)
}
override fun <T : Any?> decode(ops: DynamicOps<T>, input: T): DataResult<Pair<EnhancedPlacedFeature.Node, T>> {
return ops.getMap(input).flatMap {
val children = it.get("children")
val modifier = it.get("modifier")
val feature = it.get("feature")
if (modifier != null && feature != null) {
return@flatMap DataResult.error { "Enhanced feature placement node should have either modifier or feature, but both are present" }
} else if (modifier == null && feature == null) {
return@flatMap DataResult.error { "Enhanced feature placement node contains no modifier and no feature" }
}
val deserializeChildren: List<EnhancedPlacedFeature.Node>
if (children != null) {
val result = list.decode(ops, children)
if (result.isError)
return@flatMap DataResult.error { result.error().get().message() }
deserializeChildren = result.map { it.first }.getOrThrow()
} else {
deserializeChildren = emptyList()
}
if (modifier != null) {
return@flatMap EnhancedPlacementModifier.CODEC.decode(ops, modifier).map { Pair(EnhancedPlacedFeature.Node(deserializeChildren, Either.left(it.first)), ops.empty()) }
} else {
return@flatMap EnhancedFeature.CODEC.decode(ops, feature).map { Pair(EnhancedPlacedFeature.Node(deserializeChildren, Either.right(it.first)), ops.empty()) }
}
}
}
}
object EnhancedPlacedFeature : Feature<EnhancedPlacedFeature.Config>(
RecordCodecBuilder.create {
it.group(
Codec.INT.minRange(0).fieldOf("chunk_scan_range").forGetter(Config::chunkScanRange),
Codec.LONG.fieldOf("seed_mix").forGetter(Config::seedMix),
NodeCodec.fieldOf("root").forGetter(Config::root),
).apply(it, ::Config)
}
) {
class Node(val children: List<Node>, val contents: Either<EnhancedPlacementModifier, Holder<EnhancedFeature.Configured<*, *>>>){
fun evaluate(context: EnhancedPlacementContext) {
evaluate(context, listOf(BlockPos(context.origin.minBlockX, 0, context.origin.minBlockZ)))
}
private fun evaluate(context: EnhancedPlacementContext, positions: List<BlockPos>) {
val actualPositions = if (contents.left().isPresent) {
contents.left().get().evaluate(context, positions)
} else {
positions
}
if (contents.right().isPresent) {
context.place(actualPositions, contents.right().get().value())
}
children.forEach {
it.evaluate(context.push(), actualPositions)
}
}
}
class Config(
val chunkScanRange: Int,
val seedMix: Long,
val root: Node,
) : FeatureConfiguration {
private class GeneratedChunk : EnhancedPlacementContext.Placer {
private data class Placement(val context: EnhancedPlacementContext, val positions: List<BlockPos>, val feature: EnhancedFeature.Configured<*, *>)
// TODO: extremely inefficient
private val placed = ArrayList<Placement>()
override fun place(context: EnhancedPlacementContext, positions: List<BlockPos>, feature: EnhancedFeature.Configured<*, *>) {
placed.add(Placement(context, positions, feature))
}
fun place(context: FeaturePlaceContext<*>): Boolean {
var any = false
val pos = ChunkPos(context.origin())
for ((eContext, positions, feature) in placed) {
val filtered = positions.filter { SectionPos.blockToSectionCoord(it.x) == pos.x && SectionPos.blockToSectionCoord(it.z) == pos.z }
if (filtered.isNotEmpty()) {
any = feature.place(eContext, filtered, positions) || any
}
}
return any
}
}
private val level2cache = Caffeine.newBuilder()
.scheduler(Scheduler.systemScheduler())
.executor(Util.backgroundExecutor())
.expireAfterAccess(Duration.ofMinutes(10))
.weakKeys()
.build<WorldGenLevel, Cache<ChunkPos, GeneratedChunk>>()
private fun getCache(level: WorldGenLevel): Cache<ChunkPos, GeneratedChunk> {
return level2cache.get(level) {
Caffeine.newBuilder()
.scheduler(Scheduler.systemScheduler())
.executor(Util.backgroundExecutor())
.maximumSize(16384L)
.expireAfterWrite(Duration.ofMinutes(5))
.softValues()
.build()
}
}
private fun evaluate(context: FeaturePlaceContext<*>, chunkPos: ChunkPos): GeneratedChunk {
val bytes = FastByteArrayOutputStream()
val dataStream = DataOutputStream(bytes)
dataStream.writeLong(seedMix)
dataStream.writeInt(chunkPos.x)
dataStream.writeInt(chunkPos.z)
val hash = XXHash64()
hash.update(bytes.array, 0, bytes.length)
val random = GJRAND64RandomSource(context.level().seed, hash.digestAsLong())
val chunk = GeneratedChunk()
val enhancedContext = EnhancedPlacementContext(context.level(), context.chunkGenerator(), random, chunkPos, chunk)
root.evaluate(enhancedContext)
return chunk
}
fun place(context: FeaturePlaceContext<*>): Boolean {
val cache = getCache(context.level())
val chunkPos = ChunkPos(context.origin())
val instances = ArrayList<GeneratedChunk>()
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 <= chunkScanRange) {
val thisPos = ChunkPos(chunkPos.x + x, chunkPos.z + z)
instances.add(cache.get(thisPos) { evaluate(context, thisPos) })
}
}
}
var any = false
instances.forEach { any = it.place(context) }
return any
}
}
override fun place(context: FeaturePlaceContext<Config>): Boolean {
return context.config().place(context)
}
class Builder {
private val root: Builder?
private val children = ArrayList<Builder>()
private val contents: Either<EnhancedPlacementModifier, Holder<EnhancedFeature.Configured<*, *>>>
constructor(root: EnhancedPlacementModifier) {
this.root = null
this.contents = Either.left(root)
}
constructor(root: PlacementModifier) : this(EnhancedPlacementModifier.Wrapper(root))
constructor(root: Holder<EnhancedFeature.Configured<*, *>>) {
this.root = null
this.contents = Either.right(root)
}
constructor(root: EnhancedFeature.Configured<*, *>) {
this.root = null
this.contents = Either.right(Holder.direct(root))
}
private constructor(parent: Builder, root: PlacementModifier) : this(parent, EnhancedPlacementModifier.Wrapper(root))
private constructor(parent: Builder, root: EnhancedPlacementModifier) {
this.root = parent
this.contents = Either.left(root)
}
private constructor(parent: Builder, root: Holder<EnhancedFeature.Configured<*, *>>) {
this.root = parent
this.contents = Either.right(root)
}
private constructor(parent: Builder, root: EnhancedFeature.Configured<*, *>) {
this.root = parent
this.contents = Either.right(Holder.direct(root))
}
fun then(action: PlacementModifier): Builder {
return Builder(root ?: this, action).also(children::add)
}
fun then(action: EnhancedPlacementModifier): Builder {
return Builder(root ?: this, action).also(children::add)
}
fun then(action: EnhancedFeature.Configured<*, *>): Builder {
return Builder(root ?: this, action).also(children::add)
}
fun then(action: Holder<EnhancedFeature.Configured<*, *>>): Builder {
return Builder(root ?: this, action).also(children::add)
}
private fun buildNode(): Node {
return Node(children.map { it.buildNode() }, contents)
}
fun build(
chunkScanRange: Int,
seedMix: Long,
): PlacedFeature {
if (root != null)
return root.build(chunkScanRange, seedMix)
return PlacedFeature(Holder.direct(ConfiguredFeature(EnhancedPlacedFeature, Config(chunkScanRange, seedMix, buildNode()))), listOf())
}
}
}

View File

@ -0,0 +1,89 @@
package ru.dbotthepony.mc.otm.worldgen
import net.minecraft.core.BlockPos
import net.minecraft.util.RandomSource
import net.minecraft.world.level.ChunkPos
import net.minecraft.world.level.WorldGenLevel
import net.minecraft.world.level.chunk.ChunkGenerator
import net.minecraft.world.level.levelgen.feature.FeaturePlaceContext
import net.minecraft.world.level.levelgen.feature.configurations.FeatureConfiguration
import net.minecraft.world.level.levelgen.placement.PlacementContext
import ru.dbotthepony.kommons.util.KOptional
import ru.dbotthepony.mc.otm.worldgen.feature.EnhancedFeature
import java.util.*
import kotlin.collections.HashMap
class EnhancedPlacementContext {
fun interface Placer {
fun place(context: EnhancedPlacementContext, positions: List<BlockPos>, feature: EnhancedFeature.Configured<*, *>)
}
val level: WorldGenLevel
val generator: ChunkGenerator
val random: RandomSource
val origin: ChunkPos
private val placer: Placer
val vanillaContext: PlacementContext
private var parent: EnhancedPlacementContext? = null
private val variables = HashMap<PlacementVariable<*>, KOptional<*>>()
private fun <T> recursiveGet(index: PlacementVariable<T>): KOptional<T>? {
return variables[index] as KOptional<T>? ?: parent?.recursiveGet(index)
}
operator fun <T> get(index: PlacementVariable<T>): KOptional<T> {
return recursiveGet(index) ?: KOptional()
}
operator fun <T> set(index: PlacementVariable<T>, value: T) {
variables[index] = KOptional(value)
}
fun <T> remove(index: PlacementVariable<T>): KOptional<T> {
val old = variables.put(index, KOptional<T>())
return old as KOptional<T>? ?: KOptional()
}
fun remove(index: Collection<PlacementVariable<*>>) {
index.forEach { remove(it) }
}
constructor(
level: WorldGenLevel,
generator: ChunkGenerator,
random: RandomSource,
origin: ChunkPos,
placer: Placer,
) {
this.level = level
this.generator = generator
this.random = random
this.origin = origin
this.placer = placer
vanillaContext = PlacementContext(level, generator, Optional.empty())
}
private constructor(parent: EnhancedPlacementContext) {
this.level = parent.level
this.generator = parent.generator
this.random = parent.random
this.origin = parent.origin
this.placer = parent.placer
this.parent = parent
this.vanillaContext = parent.vanillaContext
}
fun push(): EnhancedPlacementContext {
return EnhancedPlacementContext(this)
}
fun place(positions: List<BlockPos>, feature: EnhancedFeature.Configured<*, *>) {
placer.place(this, positions, feature)
}
fun <FC : FeatureConfiguration> vanillaFeatureContext(config: FC, position: BlockPos): FeaturePlaceContext<FC> {
return FeaturePlaceContext(Optional.empty(), level, generator, random, position, config)
}
}

View File

@ -0,0 +1,9 @@
package ru.dbotthepony.mc.otm.worldgen
import net.minecraft.resources.ResourceLocation
class PlacementVariable<T>(val name: ResourceLocation) {
companion object {
}
}

View File

@ -0,0 +1,67 @@
package ru.dbotthepony.mc.otm.worldgen.feature
import com.mojang.serialization.Codec
import com.mojang.serialization.MapCodec
import net.minecraft.core.BlockPos
import net.minecraft.core.Holder
import net.minecraft.resources.RegistryFileCodec
import net.minecraft.world.level.levelgen.feature.ConfiguredFeature
import net.neoforged.bus.api.IEventBus
import net.neoforged.neoforge.registries.DataPackRegistryEvent
import ru.dbotthepony.mc.otm.registry.MBuiltInRegistries
import ru.dbotthepony.mc.otm.registry.MDeferredRegister
import ru.dbotthepony.mc.otm.registry.MRegistries
import ru.dbotthepony.mc.otm.worldgen.EnhancedPlacementContext
abstract class EnhancedFeature<FC>(codec: Codec<FC>) {
abstract fun place(context: EnhancedPlacementContext, config: FC, positions: List<BlockPos>, allPositions: List<BlockPos>): Boolean
data class Configured<F : EnhancedFeature<FC>, FC>(val feature: F, val config: FC) {
fun place(context: EnhancedPlacementContext, positions: List<BlockPos>, allPositions: List<BlockPos>): Boolean {
return feature.place(context, config, positions, allPositions)
}
}
val codec: MapCodec<Configured<*, FC>> = codec.fieldOf("config").xmap({ Configured(this, it) }, { it.config })
fun configure(config: FC) = Configured(this, config)
object Wrapper : EnhancedFeature<Holder<ConfiguredFeature<*, *>>>(ConfiguredFeature.CODEC) {
override fun place(
context: EnhancedPlacementContext,
config: Holder<ConfiguredFeature<*, *>>,
positions: List<BlockPos>,
allPositions: List<BlockPos>
): Boolean {
var any = false
positions.forEach { any = config.value().place(context.level, context.generator, context.random, it) || any }
return any
}
fun configure(config: ConfiguredFeature<*, *>) = configure(Holder.direct(config))
}
companion object {
private val registrar = MDeferredRegister(MRegistries.FEATURE)
val DIRECT_CODEC: Codec<Configured<*, *>> by lazy {
MBuiltInRegistries.FEATURE.byNameCodec().dispatch({ it.feature }, { it.codec })
}
val CODEC: Codec<Holder<Configured<*, *>>> by lazy {
RegistryFileCodec.create(MRegistries.CONFIGURED_FEATURE, DIRECT_CODEC)
}
init {
registrar.register("wrapper") { Wrapper }
}
private fun registerRegistry(event: DataPackRegistryEvent.NewRegistry) {
event.dataPackRegistry(MRegistries.CONFIGURED_FEATURE, DIRECT_CODEC)
}
internal fun register(bus: IEventBus) {
registrar.register(bus)
bus.addListener(::registerRegistry)
}
}
}

View File

@ -0,0 +1,49 @@
package ru.dbotthepony.mc.otm.worldgen.placement
import com.mojang.serialization.Codec
import com.mojang.serialization.MapCodec
import net.minecraft.core.BlockPos
import net.minecraft.world.level.levelgen.placement.PlacementModifier
import net.neoforged.bus.api.IEventBus
import ru.dbotthepony.mc.otm.registry.MBuiltInRegistries
import ru.dbotthepony.mc.otm.registry.MDeferredRegister
import ru.dbotthepony.mc.otm.registry.MRegistries
import ru.dbotthepony.mc.otm.worldgen.EnhancedPlacementContext
interface EnhancedPlacementModifier {
interface Type<T : EnhancedPlacementModifier> {
val codec: MapCodec<T>
}
fun evaluate(context: EnhancedPlacementContext, positions: List<BlockPos>): List<BlockPos>
val type: Type<*>
class Wrapper(val parent: PlacementModifier) : EnhancedPlacementModifier {
override fun evaluate(context: EnhancedPlacementContext, positions: List<BlockPos>): List<BlockPos> {
return positions.stream().flatMap { parent.getPositions(context.vanillaContext, context.random, it) }.toList()
}
override val type: Type<*>
get() = Companion
companion object : Type<Wrapper> {
override val codec: MapCodec<Wrapper> = PlacementModifier.CODEC.xmap(::Wrapper, Wrapper::parent).fieldOf("parent")
}
}
companion object {
private val registrar = MDeferredRegister(MRegistries.PLACEMENT_MODIFIER)
init {
registrar.register("wrapper") { Wrapper.Companion }
}
val CODEC: Codec<EnhancedPlacementModifier> by lazy {
MBuiltInRegistries.PLACEMENT_MODIFIER.byNameCodec().dispatch({ it.type }, { it.codec })
}
internal fun register(bus: IEventBus) {
registrar.register(bus)
}
}
}