Compare commits

..

21 Commits

Author SHA1 Message Date
e179756995
Use proper features to place enormous ores 2025-03-08 23:47:49 +07:00
df949a6861
More or less actual tritanium worm placement configuration 2025-03-08 23:32:59 +07:00
88c3fe24b0
Post placement modifiers for enormous placements 2025-03-08 22:51:43 +07:00
25a312b56f
Separate turn rate for each axis, smooth turning with specifable turn speed 2025-03-08 21:59:29 +07:00
be97560f01
Merge branch '1.21' into worldgen-placement-providers 2025-03-08 19:24:19 +07:00
85aecaf79b
Cache toInt() and toLong() results in Decimal$Regular 2025-03-08 19:23:11 +07:00
65d076f634
Bump Kommons 2025-03-08 17:45:09 +07:00
d06c32aec2
Use PCG as random source in Enormous Placement 2025-03-08 17:40:35 +07:00
3ea9a7f386
Merge branch '1.21' into worldgen-placement-providers 2025-03-08 17:35:01 +07:00
7251760800
Try PCG as otm provided random 2025-03-08 17:32:42 +07:00
3bcfd55154
Bump kommons 2025-03-08 17:30:25 +07:00
1ca2348adf
Provide PCG32 wrapper of RandomSource 2025-03-08 17:18:45 +07:00
430bc70c7e
Move PRNG implementation to Kommons
Yurie — Сегодня, в 15:09
вопрос
почему все эти рандомы не в kommons
2025-03-08 16:45:38 +07:00
8b0edc5d5f
Cope-posty 2025-03-08 14:28:51 +07:00
19e405d4f4
Use RandomGenerator methods when available 2025-03-08 14:18:11 +07:00
e1d3b36dab
Merge branch '1.21' into worldgen-placement-providers 2025-03-08 14:12:44 +07:00
322d89f2a2
Micro optimization 2025-03-08 14:12:32 +07:00
157d2c5498
Merge branch '1.21' into worldgen-placement-providers 2025-03-08 14:12:12 +07:00
ebdff6811d
Don't scramble xoshiro on fork since seed is already enough random 2025-03-08 14:11:50 +07:00
01054a05d2
Add BooleanProvider.BiasedLinear 2025-03-08 11:06:35 +07:00
a89371007f
Declare BooleanProvider as interface 2025-03-07 23:36:56 +07:00
15 changed files with 480 additions and 254 deletions

View File

@ -22,7 +22,7 @@ mixin_version=0.8.5
neogradle.subsystems.parchment.minecraftVersion=1.21.1 neogradle.subsystems.parchment.minecraftVersion=1.21.1
neogradle.subsystems.parchment.mappingsVersion=2024.11.17 neogradle.subsystems.parchment.mappingsVersion=2024.11.17
kommons_version=3.1.3 kommons_version=3.3.2
caffeine_cache_version=3.1.5 caffeine_cache_version=3.1.5
jei_version=19.16.4.171 jei_version=19.16.4.171

View File

@ -7,9 +7,8 @@ import net.minecraft.resources.ResourceKey
import net.minecraft.tags.BiomeTags import net.minecraft.tags.BiomeTags
import net.minecraft.tags.BlockTags import net.minecraft.tags.BlockTags
import net.minecraft.util.valueproviders.ClampedNormalFloat import net.minecraft.util.valueproviders.ClampedNormalFloat
import net.minecraft.util.valueproviders.ClampedNormalInt
import net.minecraft.util.valueproviders.ConstantFloat import net.minecraft.util.valueproviders.ConstantFloat
import net.minecraft.util.valueproviders.IntProvider import net.minecraft.util.valueproviders.ConstantInt
import net.minecraft.util.valueproviders.UniformFloat import net.minecraft.util.valueproviders.UniformFloat
import net.minecraft.util.valueproviders.UniformInt import net.minecraft.util.valueproviders.UniformInt
import net.minecraft.world.level.levelgen.GenerationStep import net.minecraft.world.level.levelgen.GenerationStep
@ -17,14 +16,13 @@ import net.minecraft.world.level.levelgen.VerticalAnchor
import net.minecraft.world.level.levelgen.feature.ConfiguredFeature import net.minecraft.world.level.levelgen.feature.ConfiguredFeature
import net.minecraft.world.level.levelgen.feature.Feature import net.minecraft.world.level.levelgen.feature.Feature
import net.minecraft.world.level.levelgen.feature.configurations.OreConfiguration import net.minecraft.world.level.levelgen.feature.configurations.OreConfiguration
import net.minecraft.world.level.levelgen.feature.configurations.ReplaceBlockConfiguration
import net.minecraft.world.level.levelgen.heightproviders.VeryBiasedToBottomHeight import net.minecraft.world.level.levelgen.heightproviders.VeryBiasedToBottomHeight
import net.minecraft.world.level.levelgen.placement.CountPlacement import net.minecraft.world.level.levelgen.placement.CountPlacement
import net.minecraft.world.level.levelgen.placement.HeightRangePlacement import net.minecraft.world.level.levelgen.placement.HeightRangePlacement
import net.minecraft.world.level.levelgen.placement.InSquarePlacement import net.minecraft.world.level.levelgen.placement.InSquarePlacement
import net.minecraft.world.level.levelgen.placement.PlacedFeature import net.minecraft.world.level.levelgen.placement.PlacedFeature
import net.minecraft.world.level.levelgen.placement.RarityFilter import net.minecraft.world.level.levelgen.placement.RarityFilter
import net.minecraft.world.level.levelgen.structure.templatesystem.AlwaysTrueTest
import net.minecraft.world.level.levelgen.structure.templatesystem.BlockStateMatchTest
import net.minecraft.world.level.levelgen.structure.templatesystem.TagMatchTest import net.minecraft.world.level.levelgen.structure.templatesystem.TagMatchTest
import net.neoforged.neoforge.common.world.BiomeModifier import net.neoforged.neoforge.common.world.BiomeModifier
import net.neoforged.neoforge.registries.NeoForgeRegistries import net.neoforged.neoforge.registries.NeoForgeRegistries
@ -37,6 +35,7 @@ import ru.dbotthepony.mc.otm.registry.data.MWorldGenFeatures
import ru.dbotthepony.mc.otm.worldgen.feature.BlackHolePlacerFeature import ru.dbotthepony.mc.otm.worldgen.feature.BlackHolePlacerFeature
import ru.dbotthepony.mc.otm.worldgen.feature.DebugPlacerFeature import ru.dbotthepony.mc.otm.worldgen.feature.DebugPlacerFeature
import ru.dbotthepony.mc.otm.worldgen.placement.AbstractEnormousPlacement import ru.dbotthepony.mc.otm.worldgen.placement.AbstractEnormousPlacement
import ru.dbotthepony.mc.otm.worldgen.placement.EllipsoidPlacement
import ru.dbotthepony.mc.otm.worldgen.placement.EnormousEllipsoidPlacement import ru.dbotthepony.mc.otm.worldgen.placement.EnormousEllipsoidPlacement
import ru.dbotthepony.mc.otm.worldgen.placement.WormPlacement import ru.dbotthepony.mc.otm.worldgen.placement.WormPlacement
@ -62,8 +61,8 @@ fun registerConfiguredFeatures(context: BootstrapContext<ConfiguredFeature<*, *>
) )
context.register(ConfiguredFeatures.TRITANIUM_ORE, ConfiguredFeature(Feature.ORE, OreConfiguration(target, 9))) context.register(ConfiguredFeatures.TRITANIUM_ORE, ConfiguredFeature(Feature.ORE, OreConfiguration(target, 9)))
//context.register(ConfiguredFeatures.TRITANIUM_ORE_SMALL, ConfiguredFeature(Feature.ORE, OreConfiguration(target, 3))) context.register(ConfiguredFeatures.TRITANIUM_ORE_SMALL, ConfiguredFeature(Feature.REPLACE_SINGLE_BLOCK, ReplaceBlockConfiguration(target)))
context.register(ConfiguredFeatures.TRITANIUM_ORE_SMALL, ConfiguredFeature(MWorldGenFeatures.DEBUG_PLACEMENT, DebugPlacerFeature.Config(MBlocks.TRITANIUM_ORE.defaultBlockState()))) //context.register(ConfiguredFeatures.TRITANIUM_ORE_SMALL, ConfiguredFeature(MWorldGenFeatures.DEBUG_PLACEMENT, DebugPlacerFeature.Config(MBlocks.TRITANIUM_ORE.defaultBlockState())))
} }
run { run {
@ -72,7 +71,7 @@ fun registerConfiguredFeatures(context: BootstrapContext<ConfiguredFeature<*, *>
OreConfiguration.target(deepslate, MBlocks.DEEPSLATE_DILITHIUM_ORE.defaultBlockState()), OreConfiguration.target(deepslate, MBlocks.DEEPSLATE_DILITHIUM_ORE.defaultBlockState()),
) )
context.register(ConfiguredFeatures.DILITHIUM, ConfiguredFeature(Feature.ORE, OreConfiguration(target, 3))) context.register(ConfiguredFeatures.DILITHIUM, ConfiguredFeature(Feature.REPLACE_SINGLE_BLOCK, ReplaceBlockConfiguration(target)))
} }
context.register(ConfiguredFeatures.BLACK_HOLE, ConfiguredFeature( context.register(ConfiguredFeatures.BLACK_HOLE, ConfiguredFeature(
@ -121,18 +120,34 @@ fun registerPlacedFeatures(context: BootstrapContext<PlacedFeature>) {
listOf( listOf(
WormPlacement( WormPlacement(
parameters = AbstractEnormousPlacement.Parameters( parameters = AbstractEnormousPlacement.Parameters(
chunkScanRange = 12, chunkScanRange = 24,
seedMix = 9284343575495L, seedMix = 9284343575495L,
placementModifiers = listOf( prePlacementModifiers = listOf(
RarityFilter.onAverageOnceEvery(10), RarityFilter.onAverageOnceEvery(240),
InSquarePlacement.spread(), InSquarePlacement.spread(),
HeightRangePlacement.of(StandardDeviationHeightProvider(VerticalAnchor.absolute(120), 15.0)), HeightRangePlacement.of(StandardDeviationHeightProvider(VerticalAnchor.absolute(-40), 15.0)),
),
postPlacementModifiers = listOf(
EllipsoidPlacement(
count = UniformInt.of(15, 40),
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),
)
) )
), ),
wormLength = UniformInt.of(40, 200), length = UniformInt.of(120, 400),
wormTurnChance = BooleanProvider.onceEvery(4), turnRateXY = WormPlacement.normalDistributedTurnRate(10f),
wormTurnXY = WormPlacement.normalDistributedTurnRate(60f), turnRateXZ = WormPlacement.normalDistributedTurnRate(60f),
wormTurnXZ = 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,
) )
) )
)) ))
@ -153,7 +168,7 @@ fun registerPlacedFeatures(context: BootstrapContext<PlacedFeature>) {
parameters = AbstractEnormousPlacement.Parameters( parameters = AbstractEnormousPlacement.Parameters(
chunkScanRange = 5, chunkScanRange = 5,
seedMix = 237483209523709234L, seedMix = 237483209523709234L,
placementModifiers = listOf( prePlacementModifiers = listOf(
RarityFilter.onAverageOnceEvery(120), RarityFilter.onAverageOnceEvery(120),
InSquarePlacement.spread(), InSquarePlacement.spread(),
HeightRangePlacement.of(StandardDeviationHeightProvider(VerticalAnchor.absolute(0), 15.0)), HeightRangePlacement.of(StandardDeviationHeightProvider(VerticalAnchor.absolute(0), 15.0)),

View File

@ -5,11 +5,11 @@ import net.minecraft.world.level.Level;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Mixin;
import ru.dbotthepony.mc.otm.core.IMatteryLevel; import ru.dbotthepony.mc.otm.core.IMatteryLevel;
import ru.dbotthepony.mc.otm.core.util.Xoshiro256SSRandom; import ru.dbotthepony.mc.otm.core.util.PCG32RandomSource;
@Mixin(Level.class) @Mixin(Level.class)
public abstract class LevelMixin implements IMatteryLevel { public abstract class LevelMixin implements IMatteryLevel {
public final RandomSource otm_random = new Xoshiro256SSRandom(); public final RandomSource otm_random = new PCG32RandomSource();
@Override @Override
public @NotNull RandomSource getOtmRandom() { public @NotNull RandomSource getOtmRandom() {

View File

@ -53,6 +53,7 @@ import ru.dbotthepony.mc.otm.config.MachinesConfig
import ru.dbotthepony.mc.otm.config.ServerConfig import ru.dbotthepony.mc.otm.config.ServerConfig
import ru.dbotthepony.mc.otm.config.ToolsConfig import ru.dbotthepony.mc.otm.config.ToolsConfig
import ru.dbotthepony.mc.otm.data.FlywheelMaterials import ru.dbotthepony.mc.otm.data.FlywheelMaterials
import ru.dbotthepony.mc.otm.data.world.BooleanProvider
import ru.dbotthepony.mc.otm.data.world.DecimalProvider import ru.dbotthepony.mc.otm.data.world.DecimalProvider
import ru.dbotthepony.mc.otm.entity.WitheredSkeletonSpawnHandler import ru.dbotthepony.mc.otm.entity.WitheredSkeletonSpawnHandler
import ru.dbotthepony.mc.otm.item.ChestUpgraderItem import ru.dbotthepony.mc.otm.item.ChestUpgraderItem
@ -136,6 +137,7 @@ object OverdriveThatMatters {
MOD_BUS.addListener(::registerNetworkPackets) MOD_BUS.addListener(::registerNetworkPackets)
DecimalProvider.register(MOD_BUS) DecimalProvider.register(MOD_BUS)
BooleanProvider.register(MOD_BUS)
AndroidResearchDescription.register(MOD_BUS) AndroidResearchDescription.register(MOD_BUS)
AndroidResearchDescriptions.register(MOD_BUS) AndroidResearchDescriptions.register(MOD_BUS)
AndroidResearchResult.register(MOD_BUS) AndroidResearchResult.register(MOD_BUS)

View File

@ -660,12 +660,18 @@ fun RandomSource.nextNormalDouble(stddev: Double, mean: Double): Double {
} }
fun RandomSource.nextFloat(min: Float, max: Float): Float { fun RandomSource.nextFloat(min: Float, max: Float): Float {
if (this is RandomGenerator)
return nextFloat(min, max)
require(max >= min) { "Min is bigger than max: $min vs $max" } require(max >= min) { "Min is bigger than max: $min vs $max" }
if (min == max) return min if (min == max) return min
return min + nextFloat() * (max - min) return min + nextFloat() * (max - min)
} }
fun RandomSource.nextDouble(min: Double, max: Double): Double { fun RandomSource.nextDouble(min: Double, max: Double): Double {
if (this is RandomGenerator)
return nextDouble(min, max)
require(max >= min) { "Min is bigger than max: $min vs $max" } require(max >= min) { "Min is bigger than max: $min vs $max" }
if (min == max) return min if (min == max) return min
return min + nextDouble() * (max - min) return min + nextDouble() * (max - min)

View File

@ -240,24 +240,40 @@ sealed class Decimal : Number(), Comparable<Decimal> {
return mag.signum() return mag.signum()
} }
private var intCache = 0
private var intComputed = false
override fun toInt(): Int { override fun toInt(): Int {
return if (whole > BI_INT_MAX) { if (!intComputed) {
Int.MAX_VALUE intCache = if (whole > BI_INT_MAX) {
} else if (whole < BI_INT_MIN) { Int.MAX_VALUE
Int.MIN_VALUE } else if (whole < BI_INT_MIN) {
} else { Int.MIN_VALUE
whole.toInt() } else {
whole.toInt()
}
intComputed = true
} }
return intCache
} }
private var longCache = 0L
private var longComputed = false
override fun toLong(): Long { override fun toLong(): Long {
return if (whole > BI_LONG_MAX) { if (!longComputed) {
Long.MAX_VALUE longCache = if (whole > BI_LONG_MAX) {
} else if (whole < BI_LONG_MIN) { Long.MAX_VALUE
Long.MIN_VALUE } else if (whole < BI_LONG_MIN) {
} else { Long.MIN_VALUE
whole.toLong() } else {
whole.toLong()
}
} }
return longCache
} }
override fun compareTo(other: Decimal): Int { override fun compareTo(other: Decimal): Int {

View File

@ -5,6 +5,7 @@ import net.minecraft.util.RandomSource
import net.minecraft.world.level.levelgen.MarsagliaPolarGaussian import net.minecraft.world.level.levelgen.MarsagliaPolarGaussian
import net.minecraft.world.level.levelgen.PositionalRandomFactory import net.minecraft.world.level.levelgen.PositionalRandomFactory
import net.minecraft.world.level.levelgen.RandomSupport import net.minecraft.world.level.levelgen.RandomSupport
import ru.dbotthepony.kommons.random.LCG64Random
import java.lang.StringBuilder import java.lang.StringBuilder
import java.util.random.RandomGenerator import java.util.random.RandomGenerator

View File

@ -0,0 +1,30 @@
package ru.dbotthepony.mc.otm.core.util
import net.minecraft.util.RandomSource
import java.util.random.RandomGenerator
interface IRandomSourceGenerator : RandomSource, RandomGenerator {
override fun nextInt(): Int
override fun nextInt(bound: Int): Int {
return super<RandomGenerator>.nextInt(bound)
}
override fun nextInt(origin: Int, bound: Int): Int {
return super<RandomGenerator>.nextInt(origin, bound)
}
override fun nextBoolean(): Boolean {
return super.nextBoolean()
}
override fun nextFloat(): Float {
return super.nextFloat()
}
override fun nextDouble(): Double {
return super.nextDouble()
}
override fun nextGaussian(): Double
}

View File

@ -6,8 +6,8 @@ import net.minecraft.world.level.levelgen.LegacyRandomSource
import net.minecraft.world.level.levelgen.MarsagliaPolarGaussian import net.minecraft.world.level.levelgen.MarsagliaPolarGaussian
import net.minecraft.world.level.levelgen.PositionalRandomFactory import net.minecraft.world.level.levelgen.PositionalRandomFactory
import net.minecraft.world.level.levelgen.RandomSupport import net.minecraft.world.level.levelgen.RandomSupport
import ru.dbotthepony.kommons.random.LCG64Random
import java.lang.StringBuilder import java.lang.StringBuilder
import java.util.random.RandomGenerator
/** /**
* Simple and insanely fast random number generator, which can be used for seeding (initializing internal state) of other number generators. * Simple and insanely fast random number generator, which can be used for seeding (initializing internal state) of other number generators.
@ -19,7 +19,7 @@ import java.util.random.RandomGenerator
* * Always use upper 32 bits instead of implementing BitRandomSource and sampling some upper bits * * Always use upper 32 bits instead of implementing BitRandomSource and sampling some upper bits
* * Uses all bits from provided seed * * Uses all bits from provided seed
*/ */
class LCG64Random(private var seed: Long = RandomSupport.generateUniqueSeed()) : RandomGenerator, RandomSource { class LCG64RandomSource(seed: Long = RandomSupport.generateUniqueSeed()) : LCG64Random(seed), IRandomSourceGenerator {
private val gaussian = MarsagliaPolarGaussian(this) private val gaussian = MarsagliaPolarGaussian(this)
override fun setSeed(seed: Long) { override fun setSeed(seed: Long) {
@ -27,43 +27,12 @@ class LCG64Random(private var seed: Long = RandomSupport.generateUniqueSeed()) :
gaussian.reset() gaussian.reset()
} }
override fun nextInt(): Int {
this.seed = MULTIPLIER * this.seed + INCREMENT
return this.seed.ushr(32).toInt()
}
override fun nextLong(): Long {
val a = nextInt().toLong() and 0xFFFFFFFFL
val b = nextInt().toLong() and 0xFFFFFFFFL
return a.shl(32) or b
}
override fun nextInt(bound: Int): Int {
return super<RandomGenerator>.nextInt(bound)
}
override fun nextInt(origin: Int, bound: Int): Int {
return super<RandomGenerator>.nextInt(origin, bound)
}
override fun nextBoolean(): Boolean {
return super.nextBoolean()
}
override fun nextFloat(): Float {
return super.nextFloat()
}
override fun nextDouble(): Double {
return super.nextDouble()
}
override fun nextGaussian(): Double { override fun nextGaussian(): Double {
return gaussian.nextGaussian() return gaussian.nextGaussian()
} }
override fun fork(): RandomSource { override fun fork(): RandomSource {
return LCG64Random(nextLong()) return LCG64RandomSource(nextLong())
} }
override fun forkPositional(): PositionalRandomFactory { override fun forkPositional(): PositionalRandomFactory {
@ -72,24 +41,19 @@ class LCG64Random(private var seed: Long = RandomSupport.generateUniqueSeed()) :
class Positional(val seed: Long) : PositionalRandomFactory { class Positional(val seed: Long) : PositionalRandomFactory {
override fun at(x: Int, y: Int, z: Int): RandomSource { override fun at(x: Int, y: Int, z: Int): RandomSource {
return LCG64Random(Mth.getSeed(x, y, z).xor(seed)) return LCG64RandomSource(Mth.getSeed(x, y, z).xor(seed))
} }
override fun fromHashOf(name: String): RandomSource { override fun fromHashOf(name: String): RandomSource {
return LCG64Random(name.hashCode().toLong().xor(seed)) return LCG64RandomSource(name.hashCode().toLong().xor(seed))
} }
override fun fromSeed(seed: Long): RandomSource { override fun fromSeed(seed: Long): RandomSource {
return LCG64Random(seed) return LCG64RandomSource(seed)
} }
override fun parityConfigString(builder: StringBuilder) { override fun parityConfigString(builder: StringBuilder) {
throw UnsupportedOperationException() throw UnsupportedOperationException()
} }
} }
companion object {
const val MULTIPLIER = 6364136223846793005
const val INCREMENT = 1442695040888963407
}
} }

View File

@ -0,0 +1,53 @@
package ru.dbotthepony.mc.otm.core.util
import net.minecraft.util.Mth
import net.minecraft.util.RandomSource
import net.minecraft.world.level.levelgen.LegacyRandomSource
import net.minecraft.world.level.levelgen.MarsagliaPolarGaussian
import net.minecraft.world.level.levelgen.PositionalRandomFactory
import net.minecraft.world.level.levelgen.RandomSupport
import ru.dbotthepony.kommons.random.LCG64Random
import ru.dbotthepony.kommons.random.PCG32Random
import java.lang.StringBuilder
/**
* @see PCG32Random
*/
class PCG32RandomSource(seed: Long = RandomSupport.generateUniqueSeed()) : PCG32Random(seed), IRandomSourceGenerator {
private val gaussian = MarsagliaPolarGaussian(this)
override fun setSeed(seed: Long) {
this.seed = seed
gaussian.reset()
}
override fun nextGaussian(): Double {
return gaussian.nextGaussian()
}
override fun fork(): RandomSource {
return PCG32RandomSource(nextLong())
}
override fun forkPositional(): PositionalRandomFactory {
return Positional(nextLong())
}
class Positional(val seed: Long) : PositionalRandomFactory {
override fun at(x: Int, y: Int, z: Int): RandomSource {
return PCG32RandomSource(Mth.getSeed(x, y, z).xor(seed))
}
override fun fromHashOf(name: String): RandomSource {
return PCG32RandomSource(name.hashCode().toLong().xor(seed))
}
override fun fromSeed(seed: Long): RandomSource {
return PCG32RandomSource(seed)
}
override fun parityConfigString(builder: StringBuilder) {
throw UnsupportedOperationException()
}
}
}

View File

@ -0,0 +1,93 @@
package ru.dbotthepony.mc.otm.core.util
import it.unimi.dsi.fastutil.HashCommon
import net.minecraft.util.Mth
import net.minecraft.util.RandomSource
import net.minecraft.world.level.levelgen.MarsagliaPolarGaussian
import net.minecraft.world.level.levelgen.PositionalRandomFactory
import net.minecraft.world.level.levelgen.RandomSupport
import ru.dbotthepony.kommons.random.LCG64Random
import ru.dbotthepony.kommons.random.Xoshiro256StarStarRandom
/**
* Excellent number generator with guaranteed period of 2^255
*/
class Xoshiro256Random : Xoshiro256StarStarRandom, IRandomSourceGenerator {
private val gaussian = MarsagliaPolarGaussian(this)
// raw
private constructor(s0: Long, s1: Long, s2: Long, s3: Long, marker: Nothing?): super(s0, s1, s2, s3, null)
// normal
constructor(s0: Long, s1: Long, s2: Long, s3: Long) : super(s0, s1, s2, s3)
// 64-bit seeded
constructor(seed: Long) : super(1L, 2L, 3L, 4L, null) {
setSeed(seed)
}
// completely random
constructor() : super(RandomSupport.generateUniqueSeed(), RandomSupport.generateUniqueSeed(), RandomSupport.generateUniqueSeed(), RandomSupport.generateUniqueSeed(), null)
override fun setSeed(seed: Long) {
val rng = LCG64Random(seed)
s0 = rng.nextLong()
s1 = rng.nextLong()
s2 = rng.nextLong()
s3 = rng.nextLong()
gaussian.reset()
}
override fun nextInt(): Int {
// sample upper bits
return nextLong().ushr(32).toInt()
}
override fun nextGaussian(): Double {
return gaussian.nextGaussian()
}
override fun fork(): RandomSource {
return Xoshiro256Random(nextLong(), nextLong(), nextLong(), nextLong(), null)
}
override fun forkPositional(): PositionalRandomFactory {
return Positional(nextLong(), nextLong(), nextLong(), nextLong())
}
class Positional(
val s0: Long,
val s1: Long,
val s2: Long,
val s3: Long,
) : PositionalRandomFactory {
override fun at(x: Int, y: Int, z: Int): RandomSource {
val rng = LCG64RandomSource(Mth.getSeed(x, y, z))
return Xoshiro256Random(
s0.rotateLeft(11).xor(rng.nextLong()),
s1.rotateLeft(22).xor(rng.nextLong()),
s2.rotateLeft(33).xor(rng.nextLong()),
s3.rotateLeft(44).xor(rng.nextLong()),
)
}
override fun fromHashOf(name: String): RandomSource {
return Xoshiro256Random(s0, HashCommon.murmurHash3(name.hashCode().toLong()).xor(s1), s2, s3)
}
override fun fromSeed(seed: Long): RandomSource {
return Xoshiro256Random(seed)
}
override fun parityConfigString(builder: StringBuilder) {
throw UnsupportedOperationException()
}
}
companion object {
@JvmStatic
fun raw(s0: Long, s1: Long, s2: Long, s3: Long): Xoshiro256Random {
return Xoshiro256Random(s0, s1, s2, s3, null)
}
}
}

View File

@ -1,138 +0,0 @@
package ru.dbotthepony.mc.otm.core.util
import it.unimi.dsi.fastutil.HashCommon
import net.minecraft.util.Mth
import net.minecraft.util.RandomSource
import net.minecraft.world.level.levelgen.MarsagliaPolarGaussian
import net.minecraft.world.level.levelgen.PositionalRandomFactory
import net.minecraft.world.level.levelgen.RandomSupport
import java.util.random.RandomGenerator
/**
* Excellent number generator with guaranteed period of 2^255
*/
class Xoshiro256SSRandom private constructor(
private var s0: Long,
private var s1: Long,
private var s2: Long,
private var s3: Long,
marker: Nothing?
) : RandomGenerator, RandomSource {
private val gaussian = MarsagliaPolarGaussian(this)
init {
if (s0 or s1 or s2 or s3 == 0L) {
s0 = 0x73CF3D83FFF44FF3L
s1 = 0x6412312B70F3CD37L
s2 = -0X6BB4C4E1327BFDCFL
s3 = -0X4BE0F5BB5F3F5240L
}
}
constructor(s0: Long, s1: Long, s2: Long, s3: Long) : this(s0, s1, s2, s3, null) {
// discard some values so generator can get going if provided seed was "weak"
for (i in 0 until 32)
nextLong()
}
constructor(seed: Long) : this(1L, 2L, 3L, 4L, null) {
setSeed(seed)
}
constructor() : this(RandomSupport.generateUniqueSeed(), RandomSupport.generateUniqueSeed(), RandomSupport.generateUniqueSeed(), RandomSupport.generateUniqueSeed(), null)
override fun setSeed(seed: Long) {
val rng = LCG64Random(seed)
s0 = rng.nextLong()
s1 = rng.nextLong()
s2 = rng.nextLong()
s3 = rng.nextLong()
gaussian.reset()
}
override fun nextInt(): Int {
// sample upper bits
return nextLong().ushr(32).toInt()
}
override fun nextLong(): Long {
val result = (s1 * 5).rotateLeft(7) * 9
val t = s1.shl(17)
s2 = s2.xor(s0)
s3 = s3.xor(s1)
s1 = s1.xor(s2)
s0 = s0.xor(s3)
s2 = s2.xor(t)
s3 = s3.rotateLeft(45)
return result
}
override fun nextInt(bound: Int): Int {
return super<RandomGenerator>.nextInt(bound)
}
override fun nextInt(origin: Int, bound: Int): Int {
return super<RandomGenerator>.nextInt(origin, bound)
}
override fun nextBoolean(): Boolean {
return super.nextBoolean()
}
override fun nextFloat(): Float {
return super.nextFloat()
}
override fun nextDouble(): Double {
return super.nextDouble()
}
override fun nextGaussian(): Double {
return gaussian.nextGaussian()
}
override fun fork(): RandomSource {
return Xoshiro256SSRandom(nextLong(), nextLong(), nextLong(), nextLong())
}
override fun forkPositional(): PositionalRandomFactory {
return Positional(nextLong(), nextLong(), nextLong(), nextLong())
}
class Positional(
val s0: Long,
val s1: Long,
val s2: Long,
val s3: Long,
) : PositionalRandomFactory {
override fun at(x: Int, y: Int, z: Int): RandomSource {
val rng = LCG64Random(Mth.getSeed(x, y, z))
return Xoshiro256SSRandom(
s0.rotateLeft(11).xor(rng.nextLong()),
s1.rotateLeft(22).xor(rng.nextLong()),
s2.rotateLeft(33).xor(rng.nextLong()),
s3.rotateLeft(44).xor(rng.nextLong()),
)
}
override fun fromHashOf(name: String): RandomSource {
return Xoshiro256SSRandom(s0, HashCommon.murmurHash3(name.hashCode().toLong()).xor(s1), s2, s3)
}
override fun fromSeed(seed: Long): RandomSource {
return Xoshiro256SSRandom(seed)
}
override fun parityConfigString(builder: StringBuilder) {
throw UnsupportedOperationException()
}
}
companion object {
@JvmStatic
fun raw(s0: Long, s1: Long, s2: Long, s3: Long): Xoshiro256SSRandom {
return Xoshiro256SSRandom(s0, s1, s2, s3, null)
}
}
}

View File

@ -1,33 +1,119 @@
package ru.dbotthepony.mc.otm.data.world package ru.dbotthepony.mc.otm.data.world
import com.mojang.serialization.Codec import com.mojang.serialization.Codec
import com.mojang.serialization.MapCodec
import com.mojang.serialization.codecs.RecordCodecBuilder import com.mojang.serialization.codecs.RecordCodecBuilder
import net.minecraft.util.RandomSource import net.minecraft.util.RandomSource
import net.minecraft.util.valueproviders.FloatProvider import net.neoforged.bus.api.IEventBus
import net.minecraft.util.valueproviders.UniformFloat import ru.dbotthepony.mc.otm.OverdriveThatMatters
import ru.dbotthepony.mc.otm.core.ResourceLocation
import ru.dbotthepony.mc.otm.data.codec.inRange
import ru.dbotthepony.mc.otm.registry.MDeferredRegister
import ru.dbotthepony.mc.otm.registry.RegistryDelegate
class BooleanProvider(val sampler: FloatProvider, val threshold: Float, val lessThan: Boolean = true) { interface BooleanProvider {
fun sample(random: RandomSource): Boolean { interface Type<T : BooleanProvider> {
val sampled = sampler.sample(random) val codec: MapCodec<T>
}
if (lessThan) { fun instance(): Instance
return sampled < threshold val type: Type<*>
} else {
return sampled >= threshold fun interface Instance {
fun sample(random: RandomSource): Boolean
}
/**
* Each time boolean is sampled, there is a fixed chance for it to test true
*/
class Unbiased(val chance: Float) : BooleanProvider, Instance {
constructor(chance: Int) : this(1f / chance)
override fun instance(): Instance {
return this
}
override val type: Type<*>
get() = Companion
override fun sample(random: RandomSource): Boolean {
return random.nextFloat() <= chance
}
companion object : Type<Unbiased> {
override val codec: MapCodec<Unbiased> = RecordCodecBuilder.mapCodec {
it.group(Codec.FLOAT.inRange(0f, 1f).fieldOf("chance").forGetter(Unbiased::chance)).apply(it, ::Unbiased)
}
}
}
/**
* Successful roll chance is specified as follows:
* ```
* base_chance * failures
* ```
*
* `failures` starts at 1, and increases each time roll failed, eventually reaching 100%
* if no successful rolls have been made.
*
* Once roll is successful, `failures` is reset back to 1.
*/
class BiasedLinear(val baseChance: Float) : BooleanProvider {
constructor(middle: Float, at: Int) : this(middle / at)
private class I(private val baseChance: Float) : Instance {
var lastSuccess = 1f
override fun sample(random: RandomSource): Boolean {
val success = random.nextFloat() <= lastSuccess * baseChance
if (success)
lastSuccess = 1f
else
lastSuccess += 1f
return success
}
}
override fun instance(): Instance {
return I(baseChance)
}
override val type: Type<*>
get() = Companion
companion object : Type<BiasedLinear> {
override val codec: MapCodec<BiasedLinear> = RecordCodecBuilder.mapCodec {
it.group(
Codec.FLOAT.inRange(0f, 1f).fieldOf("base_chance").forGetter(BiasedLinear::baseChance),
).apply(it, ::BiasedLinear)
}
} }
} }
companion object { companion object {
fun onceEvery(samples: Int): BooleanProvider { private val registryHolder = RegistryDelegate<Type<*>>("boolean_provider") {
return BooleanProvider(UniformFloat.of(0f, samples.toFloat()), 1f, lessThan = true) defaultKey(ResourceLocation(OverdriveThatMatters.MOD_ID, "zero"))
} }
val CODEC: Codec<BooleanProvider> = RecordCodecBuilder.create { val registry by registryHolder
it.group( val registryKey get() = registryHolder.key
FloatProvider.CODEC.fieldOf("sampler").forGetter(BooleanProvider::sampler),
Codec.FLOAT.fieldOf("threshold").forGetter(BooleanProvider::threshold), private val registrar = MDeferredRegister(registryKey)
Codec.BOOL.optionalFieldOf("less_than", true).forGetter(BooleanProvider::lessThan)
).apply(it, ::BooleanProvider) init {
registrar.register("unbiased") { Unbiased.Companion }
registrar.register("linear_bias") { BiasedLinear.Companion }
}
val CODEC: Codec<BooleanProvider> by lazy {
registry.byNameCodec().dispatch({ it.type }, { it.codec })
}
fun register(bus: IEventBus) {
bus.addListener(registryHolder::build)
registrar.register(bus)
} }
} }
} }

View File

@ -3,10 +3,9 @@ package ru.dbotthepony.mc.otm.worldgen.placement
import com.github.benmanes.caffeine.cache.Caffeine import com.github.benmanes.caffeine.cache.Caffeine
import com.github.benmanes.caffeine.cache.Scheduler import com.github.benmanes.caffeine.cache.Scheduler
import com.mojang.serialization.Codec import com.mojang.serialization.Codec
import com.mojang.serialization.DataResult
import com.mojang.serialization.MapCodec import com.mojang.serialization.MapCodec
import com.mojang.serialization.codecs.RecordCodecBuilder import com.mojang.serialization.codecs.RecordCodecBuilder
import it.unimi.dsi.fastutil.HashCommon import it.unimi.dsi.fastutil.io.FastByteArrayOutputStream
import net.minecraft.Util import net.minecraft.Util
import net.minecraft.core.BlockPos import net.minecraft.core.BlockPos
import net.minecraft.core.SectionPos import net.minecraft.core.SectionPos
@ -14,10 +13,11 @@ import net.minecraft.util.RandomSource
import net.minecraft.world.level.ChunkPos import net.minecraft.world.level.ChunkPos
import net.minecraft.world.level.levelgen.placement.PlacementContext import net.minecraft.world.level.levelgen.placement.PlacementContext
import net.minecraft.world.level.levelgen.placement.PlacementModifier import net.minecraft.world.level.levelgen.placement.PlacementModifier
import ru.dbotthepony.mc.otm.core.util.CMWCRandom import ru.dbotthepony.kommons.util.XXHash64
import ru.dbotthepony.mc.otm.core.util.Xoshiro256SSRandom import ru.dbotthepony.mc.otm.core.util.PCG32RandomSource
import ru.dbotthepony.mc.otm.data.codec.inRange
import ru.dbotthepony.mc.otm.data.codec.minRange import ru.dbotthepony.mc.otm.data.codec.minRange
import ru.dbotthepony.mc.otm.worldgen.placement.AbstractEnormousPlacement.Parameters
import java.io.DataOutputStream
import java.time.Duration import java.time.Duration
import java.util.stream.Stream import java.util.stream.Stream
import kotlin.math.sqrt import kotlin.math.sqrt
@ -26,8 +26,10 @@ import kotlin.math.sqrt
* Enormous placement base, which allows it to span over several chunks without issues. * 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 * 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 * shuffle of center point within chunks) must be provided inside [Parameters.prePlacementModifiers] list, in same order as if they were
* *before* this placement * *before* this placement
*
* If "post-processing" placements are required, better provide them as [Parameters.postPlacementModifiers].
*/ */
abstract class AbstractEnormousPlacement(val parameters: Parameters) : PlacementModifier() { abstract class AbstractEnormousPlacement(val parameters: Parameters) : PlacementModifier() {
data class Parameters( data class Parameters(
@ -42,7 +44,14 @@ abstract class AbstractEnormousPlacement(val parameters: Parameters) : Placement
/** /**
* Baseline placement modifiers, dictating how to appear in chunk * Baseline placement modifiers, dictating how to appear in chunk
*/ */
val placementModifiers: List<PlacementModifier>, 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(),
) )
private class GeneratedChunk(positions: Stream<BlockPos>) { private class GeneratedChunk(positions: Stream<BlockPos>) {
@ -73,10 +82,23 @@ abstract class AbstractEnormousPlacement(val parameters: Parameters) : Placement
.build<ChunkPos, GeneratedChunk>() .build<ChunkPos, GeneratedChunk>()
private fun computeChunk(context: PlacementContext, pos: ChunkPos): GeneratedChunk { private fun computeChunk(context: PlacementContext, pos: ChunkPos): GeneratedChunk {
val random = Xoshiro256SSRandom(context.level.seed, parameters.seedMix, pos.x.toLong(), pos.z.toLong()) val bytes = FastByteArrayOutputStream()
val dataStream = DataOutputStream(bytes)
dataStream.writeLong(context.level.seed)
dataStream.writeLong(parameters.seedMix)
dataStream.writeInt(pos.x)
dataStream.writeInt(pos.z)
val hash = XXHash64()
hash.update(bytes.array, 0, bytes.length)
val random = PCG32RandomSource(hash.digestAsLong())
var stream = Stream.of(BlockPos(pos.minBlockX, 0, pos.minBlockZ)) var stream = Stream.of(BlockPos(pos.minBlockX, 0, pos.minBlockZ))
parameters.placementModifiers.forEach { modifier -> stream = stream.flatMap { modifier.getPositions(context, random, it).sequential() } } parameters.prePlacementModifiers.forEach { modifier -> stream = stream.flatMap { modifier.getPositions(context, random, it).sequential() } }
return GeneratedChunk(stream.flatMap { getPositions(it, random) }) stream = stream.flatMap { getPositions(it, random) }
parameters.postPlacementModifiers.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> { final override fun getPositions(context: PlacementContext, random: RandomSource, pos: BlockPos): Stream<BlockPos> {
@ -104,7 +126,8 @@ abstract class AbstractEnormousPlacement(val parameters: Parameters) : Placement
it.group( it.group(
Codec.INT.minRange(0).fieldOf("chunk_scan_range").forGetter(Parameters::chunkScanRange), Codec.INT.minRange(0).fieldOf("chunk_scan_range").forGetter(Parameters::chunkScanRange),
Codec.LONG.fieldOf("seed_mix").forGetter(Parameters::seedMix), Codec.LONG.fieldOf("seed_mix").forGetter(Parameters::seedMix),
CODEC.listOf().fieldOf("placement").forGetter(Parameters::placementModifiers), CODEC.listOf().optionalFieldOf("placement", listOf()).forGetter(Parameters::prePlacementModifiers),
CODEC.listOf().optionalFieldOf("post_placement", listOf()).forGetter(Parameters::postPlacementModifiers),
).apply(it, ::Parameters) ).apply(it, ::Parameters)
} }
} }

View File

@ -1,5 +1,6 @@
package ru.dbotthepony.mc.otm.worldgen.placement package ru.dbotthepony.mc.otm.worldgen.placement
import com.mojang.serialization.Codec
import com.mojang.serialization.MapCodec import com.mojang.serialization.MapCodec
import com.mojang.serialization.codecs.RecordCodecBuilder import com.mojang.serialization.codecs.RecordCodecBuilder
import net.minecraft.core.BlockPos import net.minecraft.core.BlockPos
@ -11,29 +12,44 @@ import net.minecraft.util.valueproviders.IntProvider
import net.minecraft.util.valueproviders.UniformFloat import net.minecraft.util.valueproviders.UniformFloat
import net.minecraft.world.level.levelgen.placement.PlacementModifierType import net.minecraft.world.level.levelgen.placement.PlacementModifierType
import ru.dbotthepony.mc.otm.core.math.Vector import ru.dbotthepony.mc.otm.core.math.Vector
import ru.dbotthepony.mc.otm.core.math.angleDifference
import ru.dbotthepony.mc.otm.core.math.normalizeAngle
import ru.dbotthepony.mc.otm.core.math.plus import ru.dbotthepony.mc.otm.core.math.plus
import ru.dbotthepony.mc.otm.core.math.toBlockPos import ru.dbotthepony.mc.otm.core.math.toBlockPos
import ru.dbotthepony.mc.otm.core.nextDouble import ru.dbotthepony.mc.otm.core.nextDouble
import ru.dbotthepony.mc.otm.data.codec.minRange
import ru.dbotthepony.mc.otm.data.world.BooleanProvider import ru.dbotthepony.mc.otm.data.world.BooleanProvider
import ru.dbotthepony.mc.otm.registry.data.MPlacementModifiers import ru.dbotthepony.mc.otm.registry.data.MPlacementModifiers
import java.util.stream.Stream import java.util.stream.Stream
import kotlin.math.PI import kotlin.math.PI
import kotlin.math.absoluteValue
import kotlin.math.cos import kotlin.math.cos
import kotlin.math.sign
import kotlin.math.sin import kotlin.math.sin
class WormPlacement( class WormPlacement(
parameters: Parameters, parameters: Parameters,
val wormLength: IntProvider, val length: IntProvider,
val wormTurnChance: BooleanProvider, val turnChanceXZ: BooleanProvider,
val wormTurnXZ: FloatProvider, val turnChanceXY: BooleanProvider,
val wormTurnXY: FloatProvider, val turnSpeedXZ: FloatProvider,
val turnSpeedXY: FloatProvider,
val turnRateXZ: FloatProvider,
val turnRateXY: FloatProvider,
val initialAngleXY: FloatProvider = DEFAULT_INITIAL_ANGLE_XY,
val maxTravelDown: Int = Int.MAX_VALUE,
val maxTravelUp: Int = Int.MAX_VALUE,
) : AbstractEnormousPlacement(parameters) { ) : AbstractEnormousPlacement(parameters) {
override fun getPositions(center: BlockPos, random: RandomSource): Stream<BlockPos> { override fun getPositions(center: BlockPos, random: RandomSource): Stream<BlockPos> {
var position = Vector.ZERO var position = Vector.ZERO
val maxDistance = wormLength.sample(random) val maxDistance = length.sample(random)
// determine initial worm facing angle // determine initial worm facing angle
var xzRotation = random.nextDouble(-PI / 2.0, PI / 2.0) var xzRotation = random.nextDouble(-PI / 2.0, PI / 2.0)
var xyRotation = random.nextDouble(-PI / 16.0, PI / 16.0) var xyRotation = normalizeAngle(initialAngleXY.sample(random).toDouble())
// determine initial "turn to" angle
var xzTargetRotation = random.nextDouble(-PI / 2.0, PI / 2.0)
var xyTargetRotation = normalizeAngle(initialAngleXY.sample(random).toDouble())
var xzSin = sin(xzRotation) var xzSin = sin(xzRotation)
var xzCos = cos(xzRotation) var xzCos = cos(xzRotation)
@ -43,21 +59,64 @@ class WormPlacement(
var prevPos = position.toBlockPos() var prevPos = position.toBlockPos()
positions.add(prevPos) positions.add(prevPos)
val turnChanceXZ = turnChanceXZ.instance()
val turnChanceXY = turnChanceXY.instance()
for (traveledDistance in 0 .. maxDistance) { for (traveledDistance in 0 .. maxDistance) {
// wormy turn // wormy turn
if (wormTurnChance.sample(random)) { if (turnChanceXZ.sample(random)) {
// wormy angle // wormy angle
// TODO: smooth turning, instead of snapping to new angle make it gradually face new angle xzTargetRotation += turnRateXZ.sample(random)
xzRotation += wormTurnXZ.sample(random) xzTargetRotation = normalizeAngle(xzTargetRotation)
xyRotation += wormTurnXY.sample(random) }
// wormy-o!
if (turnChanceXY.sample(random)) {
// worm?
xyTargetRotation += turnRateXY.sample(random)
xzTargetRotation = normalizeAngle(xzTargetRotation)
}
if (xzTargetRotation != xzRotation) {
val diff = angleDifference(xzTargetRotation, xzRotation)
val abs = diff.absoluteValue
val speed = turnSpeedXZ.sample(random).toDouble()
if (abs <= speed)
xzRotation = xzTargetRotation
else
xzRotation += speed * diff.sign
xzSin = sin(xzRotation) xzSin = sin(xzRotation)
xzCos = cos(xzRotation) xzCos = cos(xzRotation)
}
if (xyTargetRotation != xyRotation) {
val diff = angleDifference(xyTargetRotation, xyRotation)
val abs = diff.absoluteValue
val speed = turnSpeedXZ.sample(random).toDouble()
if (abs <= speed)
xyRotation = xyTargetRotation
else
xyRotation += speed * diff.sign
xySin = sin(xyRotation) xySin = sin(xyRotation)
} }
// advance worm // advance worm
position += Vector(xzCos, xySin, xzSin) position += Vector(xzCos, xySin, xzSin)
if (position.y > maxTravelUp) {
xyTargetRotation = 0.0
xySin = 0.0
position = Vector(position.x, maxTravelUp.toDouble(), position.z)
} else if (position.y < -maxTravelDown) {
xyTargetRotation = 0.0
xySin = 0.0
position = Vector(position.x, -maxTravelDown.toDouble(), position.z)
}
val calc = position.toBlockPos() val calc = position.toBlockPos()
if (calc != prevPos) { if (calc != prevPos) {
@ -85,11 +144,21 @@ class WormPlacement(
if (sum != value) { if (sum != value) {
return sum return sum
} else { } else {
i += 1f i *= 2f
} }
} }
} }
val DEFAULT_INITIAL_ANGLE_XY: UniformFloat = UniformFloat.of(-PI.toFloat() / 16f, increment(PI.toFloat() / 16f))
fun constantTurnRate(degrees: Float): FloatProvider {
return constantTurnRateRadians(degrees * DEGREES_TO_RADIANS)
}
fun constantTurnRateRadians(rad: Float): FloatProvider {
return ConstantFloat.of(rad)
}
fun uniformTurnRate(degrees: Float): FloatProvider { fun uniformTurnRate(degrees: Float): FloatProvider {
return uniformTurnRateRadians(degrees * DEGREES_TO_RADIANS) return uniformTurnRateRadians(degrees * DEGREES_TO_RADIANS)
} }
@ -115,10 +184,16 @@ class WormPlacement(
val CODEC: MapCodec<WormPlacement> = RecordCodecBuilder.mapCodec { val CODEC: MapCodec<WormPlacement> = RecordCodecBuilder.mapCodec {
it.group( it.group(
PARAMETERS_CODEC.forGetter(WormPlacement::parameters), PARAMETERS_CODEC.forGetter(WormPlacement::parameters),
IntProvider.CODEC.fieldOf("length").forGetter(WormPlacement::wormLength), IntProvider.CODEC.fieldOf("length").forGetter(WormPlacement::length),
BooleanProvider.CODEC.fieldOf("turn_chance").forGetter(WormPlacement::wormTurnChance), BooleanProvider.CODEC.fieldOf("turn_chance_xz").forGetter(WormPlacement::turnChanceXZ),
FloatProvider.CODEC.fieldOf("turn_xz").forGetter(WormPlacement::wormTurnXZ), BooleanProvider.CODEC.fieldOf("turn_chance_xy").forGetter(WormPlacement::turnChanceXY),
FloatProvider.CODEC.fieldOf("turn_xy").forGetter(WormPlacement::wormTurnXY), FloatProvider.CODEC.fieldOf("turn_speed_xz").forGetter(WormPlacement::turnSpeedXZ),
FloatProvider.CODEC.fieldOf("turn_speed_xy").forGetter(WormPlacement::turnSpeedXY),
FloatProvider.CODEC.fieldOf("turn_rate_xz").forGetter(WormPlacement::turnRateXZ),
FloatProvider.CODEC.fieldOf("turn_rate_xy").forGetter(WormPlacement::turnRateXY),
FloatProvider.CODEC.optionalFieldOf("initial_angle_xy", DEFAULT_INITIAL_ANGLE_XY).forGetter(WormPlacement::initialAngleXY),
Codec.INT.minRange(1).optionalFieldOf("max_travel_down", Int.MAX_VALUE).forGetter(WormPlacement::maxTravelDown),
Codec.INT.minRange(1).optionalFieldOf("max_travel_up", Int.MAX_VALUE).forGetter(WormPlacement::maxTravelUp),
).apply(it, ::WormPlacement) ).apply(it, ::WormPlacement)
} }
} }