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.mappingsVersion=2024.11.17
kommons_version=3.1.3
kommons_version=3.3.2
caffeine_cache_version=3.1.5
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.BlockTags
import net.minecraft.util.valueproviders.ClampedNormalFloat
import net.minecraft.util.valueproviders.ClampedNormalInt
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.UniformInt
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.Feature
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.placement.CountPlacement
import net.minecraft.world.level.levelgen.placement.HeightRangePlacement
import net.minecraft.world.level.levelgen.placement.InSquarePlacement
import net.minecraft.world.level.levelgen.placement.PlacedFeature
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.neoforged.neoforge.common.world.BiomeModifier
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.DebugPlacerFeature
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.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_SMALL, ConfiguredFeature(Feature.ORE, OreConfiguration(target, 3)))
context.register(ConfiguredFeatures.TRITANIUM_ORE_SMALL, ConfiguredFeature(MWorldGenFeatures.DEBUG_PLACEMENT, DebugPlacerFeature.Config(MBlocks.TRITANIUM_ORE.defaultBlockState())))
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())))
}
run {
@ -72,7 +71,7 @@ fun registerConfiguredFeatures(context: BootstrapContext<ConfiguredFeature<*, *>
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(
@ -121,18 +120,34 @@ fun registerPlacedFeatures(context: BootstrapContext<PlacedFeature>) {
listOf(
WormPlacement(
parameters = AbstractEnormousPlacement.Parameters(
chunkScanRange = 12,
chunkScanRange = 24,
seedMix = 9284343575495L,
placementModifiers = listOf(
RarityFilter.onAverageOnceEvery(10),
prePlacementModifiers = listOf(
RarityFilter.onAverageOnceEvery(240),
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),
wormTurnChance = BooleanProvider.onceEvery(4),
wormTurnXY = WormPlacement.normalDistributedTurnRate(60f),
wormTurnXZ = WormPlacement.normalDistributedTurnRate(60f),
length = UniformInt.of(120, 400),
turnRateXY = WormPlacement.normalDistributedTurnRate(10f),
turnRateXZ = WormPlacement.normalDistributedTurnRate(60f),
turnSpeedXZ = WormPlacement.constantTurnRate(10f),
turnSpeedXY = WormPlacement.constantTurnRate(4f),
turnChanceXY = BooleanProvider.Unbiased(6),
turnChanceXZ = BooleanProvider.BiasedLinear(0.6f, 5),
maxTravelUp = 16,
maxTravelDown = 16,
)
)
))
@ -153,7 +168,7 @@ fun registerPlacedFeatures(context: BootstrapContext<PlacedFeature>) {
parameters = AbstractEnormousPlacement.Parameters(
chunkScanRange = 5,
seedMix = 237483209523709234L,
placementModifiers = listOf(
prePlacementModifiers = listOf(
RarityFilter.onAverageOnceEvery(120),
InSquarePlacement.spread(),
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.spongepowered.asm.mixin.Mixin;
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)
public abstract class LevelMixin implements IMatteryLevel {
public final RandomSource otm_random = new Xoshiro256SSRandom();
public final RandomSource otm_random = new PCG32RandomSource();
@Override
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.ToolsConfig
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.entity.WitheredSkeletonSpawnHandler
import ru.dbotthepony.mc.otm.item.ChestUpgraderItem
@ -136,6 +137,7 @@ object OverdriveThatMatters {
MOD_BUS.addListener(::registerNetworkPackets)
DecimalProvider.register(MOD_BUS)
BooleanProvider.register(MOD_BUS)
AndroidResearchDescription.register(MOD_BUS)
AndroidResearchDescriptions.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 {
if (this is RandomGenerator)
return nextFloat(min, max)
require(max >= min) { "Min is bigger than max: $min vs $max" }
if (min == max) return min
return min + nextFloat() * (max - min)
}
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" }
if (min == max) return min
return min + nextDouble() * (max - min)

View File

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

View File

@ -5,6 +5,7 @@ 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 java.lang.StringBuilder
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.PositionalRandomFactory
import net.minecraft.world.level.levelgen.RandomSupport
import ru.dbotthepony.kommons.random.LCG64Random
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.
@ -19,7 +19,7 @@ import java.util.random.RandomGenerator
* * Always use upper 32 bits instead of implementing BitRandomSource and sampling some upper bits
* * 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)
override fun setSeed(seed: Long) {
@ -27,43 +27,12 @@ class LCG64Random(private var seed: Long = RandomSupport.generateUniqueSeed()) :
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 {
return gaussian.nextGaussian()
}
override fun fork(): RandomSource {
return LCG64Random(nextLong())
return LCG64RandomSource(nextLong())
}
override fun forkPositional(): PositionalRandomFactory {
@ -72,24 +41,19 @@ class LCG64Random(private var seed: Long = RandomSupport.generateUniqueSeed()) :
class Positional(val seed: Long) : PositionalRandomFactory {
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 {
return LCG64Random(name.hashCode().toLong().xor(seed))
return LCG64RandomSource(name.hashCode().toLong().xor(seed))
}
override fun fromSeed(seed: Long): RandomSource {
return LCG64Random(seed)
return LCG64RandomSource(seed)
}
override fun parityConfigString(builder: StringBuilder) {
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
import com.mojang.serialization.Codec
import com.mojang.serialization.MapCodec
import com.mojang.serialization.codecs.RecordCodecBuilder
import net.minecraft.util.RandomSource
import net.minecraft.util.valueproviders.FloatProvider
import net.minecraft.util.valueproviders.UniformFloat
import net.neoforged.bus.api.IEventBus
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) {
fun sample(random: RandomSource): Boolean {
val sampled = sampler.sample(random)
interface BooleanProvider {
interface Type<T : BooleanProvider> {
val codec: MapCodec<T>
}
if (lessThan) {
return sampled < threshold
} else {
return sampled >= threshold
fun instance(): Instance
val type: Type<*>
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 {
fun onceEvery(samples: Int): BooleanProvider {
return BooleanProvider(UniformFloat.of(0f, samples.toFloat()), 1f, lessThan = true)
private val registryHolder = RegistryDelegate<Type<*>>("boolean_provider") {
defaultKey(ResourceLocation(OverdriveThatMatters.MOD_ID, "zero"))
}
val CODEC: Codec<BooleanProvider> = RecordCodecBuilder.create {
it.group(
FloatProvider.CODEC.fieldOf("sampler").forGetter(BooleanProvider::sampler),
Codec.FLOAT.fieldOf("threshold").forGetter(BooleanProvider::threshold),
Codec.BOOL.optionalFieldOf("less_than", true).forGetter(BooleanProvider::lessThan)
).apply(it, ::BooleanProvider)
val registry by registryHolder
val registryKey get() = registryHolder.key
private val registrar = MDeferredRegister(registryKey)
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.Scheduler
import com.mojang.serialization.Codec
import com.mojang.serialization.DataResult
import com.mojang.serialization.MapCodec
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.core.BlockPos
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.levelgen.placement.PlacementContext
import net.minecraft.world.level.levelgen.placement.PlacementModifier
import ru.dbotthepony.mc.otm.core.util.CMWCRandom
import ru.dbotthepony.mc.otm.core.util.Xoshiro256SSRandom
import ru.dbotthepony.mc.otm.data.codec.inRange
import ru.dbotthepony.kommons.util.XXHash64
import ru.dbotthepony.mc.otm.core.util.PCG32RandomSource
import ru.dbotthepony.mc.otm.data.codec.minRange
import ru.dbotthepony.mc.otm.worldgen.placement.AbstractEnormousPlacement.Parameters
import java.io.DataOutputStream
import java.time.Duration
import java.util.stream.Stream
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.
*
* 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
*
* If "post-processing" placements are required, better provide them as [Parameters.postPlacementModifiers].
*/
abstract class AbstractEnormousPlacement(val parameters: Parameters) : PlacementModifier() {
data class Parameters(
@ -42,7 +44,14 @@ abstract class AbstractEnormousPlacement(val parameters: Parameters) : Placement
/**
* 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>) {
@ -73,10 +82,23 @@ abstract class AbstractEnormousPlacement(val parameters: Parameters) : Placement
.build<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))
parameters.placementModifiers.forEach { modifier -> stream = stream.flatMap { modifier.getPositions(context, random, it).sequential() } }
return GeneratedChunk(stream.flatMap { getPositions(it, random) })
parameters.prePlacementModifiers.forEach { modifier -> stream = stream.flatMap { modifier.getPositions(context, random, it).sequential() } }
stream = stream.flatMap { getPositions(it, random) }
parameters.postPlacementModifiers.forEach { modifier -> stream = stream.flatMap { modifier.getPositions(context, random, it).sequential() } }
return GeneratedChunk(stream)
}
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(
Codec.INT.minRange(0).fieldOf("chunk_scan_range").forGetter(Parameters::chunkScanRange),
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)
}
}

View File

@ -1,5 +1,6 @@
package ru.dbotthepony.mc.otm.worldgen.placement
import com.mojang.serialization.Codec
import com.mojang.serialization.MapCodec
import com.mojang.serialization.codecs.RecordCodecBuilder
import net.minecraft.core.BlockPos
@ -11,29 +12,44 @@ import net.minecraft.util.valueproviders.IntProvider
import net.minecraft.util.valueproviders.UniformFloat
import net.minecraft.world.level.levelgen.placement.PlacementModifierType
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.toBlockPos
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.registry.data.MPlacementModifiers
import java.util.stream.Stream
import kotlin.math.PI
import kotlin.math.absoluteValue
import kotlin.math.cos
import kotlin.math.sign
import kotlin.math.sin
class WormPlacement(
parameters: Parameters,
val wormLength: IntProvider,
val wormTurnChance: BooleanProvider,
val wormTurnXZ: FloatProvider,
val wormTurnXY: FloatProvider,
val length: IntProvider,
val turnChanceXZ: BooleanProvider,
val turnChanceXY: BooleanProvider,
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) {
override fun getPositions(center: BlockPos, random: RandomSource): Stream<BlockPos> {
var position = Vector.ZERO
val maxDistance = wormLength.sample(random)
val maxDistance = length.sample(random)
// determine initial worm facing angle
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 xzCos = cos(xzRotation)
@ -43,21 +59,64 @@ class WormPlacement(
var prevPos = position.toBlockPos()
positions.add(prevPos)
val turnChanceXZ = turnChanceXZ.instance()
val turnChanceXY = turnChanceXY.instance()
for (traveledDistance in 0 .. maxDistance) {
// wormy turn
if (wormTurnChance.sample(random)) {
if (turnChanceXZ.sample(random)) {
// wormy angle
// TODO: smooth turning, instead of snapping to new angle make it gradually face new angle
xzRotation += wormTurnXZ.sample(random)
xyRotation += wormTurnXY.sample(random)
xzTargetRotation += turnRateXZ.sample(random)
xzTargetRotation = normalizeAngle(xzTargetRotation)
}
// 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)
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)
}
// advance worm
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()
if (calc != prevPos) {
@ -85,11 +144,21 @@ class WormPlacement(
if (sum != value) {
return sum
} 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 {
return uniformTurnRateRadians(degrees * DEGREES_TO_RADIANS)
}
@ -115,10 +184,16 @@ class WormPlacement(
val CODEC: MapCodec<WormPlacement> = RecordCodecBuilder.mapCodec {
it.group(
PARAMETERS_CODEC.forGetter(WormPlacement::parameters),
IntProvider.CODEC.fieldOf("length").forGetter(WormPlacement::wormLength),
BooleanProvider.CODEC.fieldOf("turn_chance").forGetter(WormPlacement::wormTurnChance),
FloatProvider.CODEC.fieldOf("turn_xz").forGetter(WormPlacement::wormTurnXZ),
FloatProvider.CODEC.fieldOf("turn_xy").forGetter(WormPlacement::wormTurnXY),
IntProvider.CODEC.fieldOf("length").forGetter(WormPlacement::length),
BooleanProvider.CODEC.fieldOf("turn_chance_xz").forGetter(WormPlacement::turnChanceXZ),
BooleanProvider.CODEC.fieldOf("turn_chance_xy").forGetter(WormPlacement::turnChanceXY),
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)
}
}