diff --git a/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/DataGen.kt b/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/DataGen.kt index 52fb18559..5817f4177 100644 --- a/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/DataGen.kt +++ b/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/DataGen.kt @@ -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) diff --git a/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/WorldGen.kt b/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/WorldGen.kt index cdb54e788..23693c5db 100644 --- a/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/WorldGen.kt +++ b/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/WorldGen.kt @@ -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> { return ResourceKey.create(Registries.CONFIGURED_FEATURE, modLocation(name)) } + + private fun ekey(name: String): ResourceKey> { + return ResourceKey.create(MRegistries.CONFIGURED_FEATURE, modLocation(name)) + } +} + +fun registerEnhancedConfiguredFeatures(context: BootstrapContext>) { + 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>) { @@ -65,15 +86,6 @@ fun registerConfiguredFeatures(context: BootstrapContext //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) { 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) { } 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) { ) ) )) + */ + + 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) diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/OverdriveThatMatters.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/OverdriveThatMatters.kt index a3924ef6e..f7914ecaa 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/OverdriveThatMatters.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/OverdriveThatMatters.kt @@ -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) diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MBuiltInRegistries.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MBuiltInRegistries.kt index 699e7497e..4a2577f6e 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MBuiltInRegistries.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MBuiltInRegistries.kt @@ -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) } } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MRegistries.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MRegistries.kt index 3fab1b53e..204facf30 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MRegistries.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MRegistries.kt @@ -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 k(name: String): ResourceKey> { @@ -28,4 +31,7 @@ object MRegistries { val ANDROID_FEATURE = k>("android_feature") val STACK_TYPE = k>("stack_type") val BOOLEAN_PROVIDER = k>("boolean_provider") + val FEATURE = k>("feature") + val CONFIGURED_FEATURE = k>("configured_feature") + val PLACEMENT_MODIFIER = k>("placement_modifier") } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/registry/data/MWorldGenFeatures.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/registry/data/MWorldGenFeatures.kt index d570b24aa..2f7d563ef 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/registry/data/MWorldGenFeatures.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/registry/data/MWorldGenFeatures.kt @@ -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 } } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/worldgen/EnhancedPlacedFeature.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/worldgen/EnhancedPlacedFeature.kt new file mode 100644 index 000000000..5cac733d5 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/worldgen/EnhancedPlacedFeature.kt @@ -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 { + private val list = Codec.list(this) + + override fun encode(input: EnhancedPlacedFeature.Node, ops: DynamicOps, prefix: T): DataResult { + val result = HashMap() + + 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 decode(ops: DynamicOps, input: T): DataResult> { + 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 + + 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( + 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, val contents: Either>>){ + fun evaluate(context: EnhancedPlacementContext) { + evaluate(context, listOf(BlockPos(context.origin.minBlockX, 0, context.origin.minBlockZ))) + } + + private fun evaluate(context: EnhancedPlacementContext, positions: List) { + 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, val feature: EnhancedFeature.Configured<*, *>) + + // TODO: extremely inefficient + private val placed = ArrayList() + + override fun place(context: EnhancedPlacementContext, positions: List, 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>() + + private fun getCache(level: WorldGenLevel): Cache { + 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() + + 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): Boolean { + return context.config().place(context) + } + + class Builder { + private val root: Builder? + private val children = ArrayList() + private val contents: Either>> + + constructor(root: EnhancedPlacementModifier) { + this.root = null + this.contents = Either.left(root) + } + + constructor(root: PlacementModifier) : this(EnhancedPlacementModifier.Wrapper(root)) + + constructor(root: Holder>) { + 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>) { + 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>): 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()) + } + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/worldgen/EnhancedPlacementContext.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/worldgen/EnhancedPlacementContext.kt new file mode 100644 index 000000000..3cdd33f4d --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/worldgen/EnhancedPlacementContext.kt @@ -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, 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, KOptional<*>>() + + private fun recursiveGet(index: PlacementVariable): KOptional? { + return variables[index] as KOptional? ?: parent?.recursiveGet(index) + } + + operator fun get(index: PlacementVariable): KOptional { + return recursiveGet(index) ?: KOptional() + } + + operator fun set(index: PlacementVariable, value: T) { + variables[index] = KOptional(value) + } + + fun remove(index: PlacementVariable): KOptional { + val old = variables.put(index, KOptional()) + return old as KOptional? ?: KOptional() + } + + fun remove(index: Collection>) { + 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, feature: EnhancedFeature.Configured<*, *>) { + placer.place(this, positions, feature) + } + + fun vanillaFeatureContext(config: FC, position: BlockPos): FeaturePlaceContext { + return FeaturePlaceContext(Optional.empty(), level, generator, random, position, config) + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/worldgen/PlacementVariable.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/worldgen/PlacementVariable.kt new file mode 100644 index 000000000..d0d325f2f --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/worldgen/PlacementVariable.kt @@ -0,0 +1,9 @@ +package ru.dbotthepony.mc.otm.worldgen + +import net.minecraft.resources.ResourceLocation + +class PlacementVariable(val name: ResourceLocation) { + companion object { + + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/worldgen/feature/EnhancedFeature.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/worldgen/feature/EnhancedFeature.kt new file mode 100644 index 000000000..606eb5ce4 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/worldgen/feature/EnhancedFeature.kt @@ -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(codec: Codec) { + abstract fun place(context: EnhancedPlacementContext, config: FC, positions: List, allPositions: List): Boolean + + data class Configured, FC>(val feature: F, val config: FC) { + fun place(context: EnhancedPlacementContext, positions: List, allPositions: List): Boolean { + return feature.place(context, config, positions, allPositions) + } + } + + val codec: MapCodec> = codec.fieldOf("config").xmap({ Configured(this, it) }, { it.config }) + fun configure(config: FC) = Configured(this, config) + + object Wrapper : EnhancedFeature>>(ConfiguredFeature.CODEC) { + override fun place( + context: EnhancedPlacementContext, + config: Holder>, + positions: List, + allPositions: List + ): 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> by lazy { + MBuiltInRegistries.FEATURE.byNameCodec().dispatch({ it.feature }, { it.codec }) + } + + val CODEC: Codec>> 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) + } + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/worldgen/placement/EnhancedPlacementModifier.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/worldgen/placement/EnhancedPlacementModifier.kt new file mode 100644 index 000000000..e2f1b81d0 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/worldgen/placement/EnhancedPlacementModifier.kt @@ -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 { + val codec: MapCodec + } + + fun evaluate(context: EnhancedPlacementContext, positions: List): List + val type: Type<*> + + class Wrapper(val parent: PlacementModifier) : EnhancedPlacementModifier { + override fun evaluate(context: EnhancedPlacementContext, positions: List): List { + return positions.stream().flatMap { parent.getPositions(context.vanillaContext, context.random, it) }.toList() + } + + override val type: Type<*> + get() = Companion + + companion object : Type { + override val codec: MapCodec = 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 by lazy { + MBuiltInRegistries.PLACEMENT_MODIFIER.byNameCodec().dispatch({ it.type }, { it.codec }) + } + + internal fun register(bus: IEventBus) { + registrar.register(bus) + } + } +}