Compare commits

...

98 Commits

Author SHA1 Message Date
3cdcb411f7
Ooprs!!1 2025-04-03 11:09:13 +07:00
174a86b33c
Fix tritanium ellipsoid being biased 2025-04-01 16:06:20 +07:00
f451ea4df4
Merge branch '1.21' into worldgen-placement-providers 2025-03-31 20:58:47 +07:00
74d48a7dc3
Update references after merge 2025-03-31 13:10:12 +07:00
5b463e8adb
Merge branch '1.21' into worldgen-placement-providers
# Conflicts:
#	src/data/kotlin/ru/dbotthepony/mc/otm/datagen/WorldGen.kt
#	src/main/kotlin/ru/dbotthepony/mc/otm/OverdriveThatMatters.kt
#	src/main/kotlin/ru/dbotthepony/mc/otm/registry/data/MWorldGenFeatures.kt
#	src/main/kotlin/ru/dbotthepony/mc/otm/server/world/EnhancedPlacedFeature.kt
#	src/main/kotlin/ru/dbotthepony/mc/otm/server/world/EnhancedPlacementContext.kt
#	src/main/kotlin/ru/dbotthepony/mc/otm/server/world/Ext.kt
#	src/main/kotlin/ru/dbotthepony/mc/otm/server/world/PlacementVariable.kt
#	src/main/kotlin/ru/dbotthepony/mc/otm/server/world/PlacementVariableMap.kt
#	src/main/kotlin/ru/dbotthepony/mc/otm/server/world/feature/DebugPlacerFeature.kt
#	src/main/kotlin/ru/dbotthepony/mc/otm/server/world/feature/EnhancedFeature.kt
#	src/main/kotlin/ru/dbotthepony/mc/otm/server/world/placement/ChainPlacement.kt
#	src/main/kotlin/ru/dbotthepony/mc/otm/server/world/placement/EllipsoidPlacement.kt
#	src/main/kotlin/ru/dbotthepony/mc/otm/server/world/placement/EnhancedChainPlacement.kt
#	src/main/kotlin/ru/dbotthepony/mc/otm/server/world/placement/EnhancedCountPlacement.kt
#	src/main/kotlin/ru/dbotthepony/mc/otm/server/world/placement/EnhancedPlacement.kt
#	src/main/kotlin/ru/dbotthepony/mc/otm/server/world/placement/EnhancedSplitPlacement.kt
#	src/main/kotlin/ru/dbotthepony/mc/otm/server/world/placement/SplitPlacement.kt
#	src/main/kotlin/ru/dbotthepony/mc/otm/server/world/placement/StandardDeviationHeightProvider.kt
#	src/main/kotlin/ru/dbotthepony/mc/otm/server/world/placement/WormPlacement.kt
2025-03-31 13:02:27 +07:00
0743ea2c9a
Update method references after merge 2025-03-31 08:17:31 +07:00
347a5465cc
Move vec3i hash strategy to util.collect 2025-03-31 08:15:38 +07:00
526578f76c
Merge branch '1.21' into worldgen-placement-providers 2025-03-31 08:14:57 +07:00
ac1c9a1ad4
Add ellipsoid placement rotation 2025-03-30 15:13:56 +07:00
9cfbab6c3c
Mark BooleanProvider impls as data classes so they can be compared 2025-03-29 22:06:15 +07:00
07e6369454
Merge branch '1.21' into worldgen-placement-providers 2025-03-29 22:05:15 +07:00
ddab73bad1
Merge branch '1.21' into worldgen-placement-providers 2025-03-29 18:20:37 +07:00
a03c2d5eb4
Remove BlockPosSet since it is not used, twice as slow as regular tree sets and practically dont give much memory savings 2025-03-26 16:00:41 +07:00
7ca0ba1ce5
TreeSet is faster than ObjectRBTreeSet for some reason 2025-03-26 15:58:47 +07:00
7a5a979169
Improve enhanced placed feature performance 2025-03-26 13:15:26 +07:00
e2369b3a24
Unify placement modifier and feature under single "placement" interface, greatly simplifying and empowering things at the same time 2025-03-26 11:03:48 +07:00
2e104dcbce
Create new variable map for each evaluation so leaks wont have lasting effects 2025-03-25 20:38:11 +07:00
159125fb4b
Per-block variable storage in enhanced placement 2025-03-25 18:59:53 +07:00
727111cf4a
Fix blockposset subset forgetting blocks at negative y values 2025-03-25 14:13:19 +07:00
d08a928e04
a 2025-03-25 13:22:29 +07:00
398bd532f4
Remove "inefficinet" comment since placements are now efficiently filtered 2025-03-25 12:26:43 +07:00
7e570747c2
Bring back bigger caches since memory usage now is normal again 2025-03-25 12:25:53 +07:00
d683ea1e38
More efficient ellipsoid placement 2025-03-25 12:16:25 +07:00
f74dbbd84a
Fix level being wrong when placing features in-world 2025-03-25 12:10:28 +07:00
2993ae61ca
man. 2025-03-25 11:52:59 +07:00
9577e205e7
Set last returned index to -1 when removing 2025-03-25 11:52:01 +07:00
ba83b89476
Ooprs!!1 2025-03-25 11:11:23 +07:00
bf6fec7753
Create shared state in EnhancedPlacementContext for memory efficiency 2025-03-25 11:09:47 +07:00
0af5fb7301
Use LinkedList in generated chunk for memory efficiency 2025-03-25 11:09:22 +07:00
a4e40bd464
Provide BlockPosSet for vastly improved memory efficiency 2025-03-25 11:03:35 +07:00
07b295ce45
Add enhanced variants for placement modifiers 2025-03-25 00:07:55 +07:00
ba492e0cee
Merge branch '1.21' into worldgen-placement-providers 2025-03-24 23:34:18 +07:00
dca02893a4
Make generation cache expire much quicker 2025-03-24 21:11:59 +07:00
ab6e3ad87f
Use sets only for final placements, use lists for intermediate 2025-03-24 20:16:57 +07:00
78c0a5d717
Fix platform declaration clash 2025-03-24 19:32:18 +07:00
e150b57b65
Add passthrough placement modifier, and add argument-less placed feature builder 2025-03-24 19:30:17 +07:00
1e6e38ea7d
Remove enormous placement 2025-03-24 19:27:36 +07:00
1acd105925
Move tritanium worms to enhanced placed feature 2025-03-24 19:27:03 +07:00
5d05fe3bb4
Use tree set 2025-03-24 19:22:35 +07:00
bc81103e38
Use sets for block positions instead of lists
it makes no sense to give same position twice
2025-03-24 19:07:25 +07:00
18f9bc2654
Enhanced placements prototyping 2025-03-24 18:42:52 +07:00
03a1ab9197
Merge branch '1.21' into worldgen-placement-providers 2025-03-24 18:15:59 +07:00
055ca7ec43
Merge branch '1.21' into worldgen-placement-providers 2025-03-24 18:15:05 +07:00
ca67e796da
Update boolean provider to use MRegistries 2025-03-24 14:01:32 +07:00
9ee561d0fd
Merge branch '1.21' into worldgen-placement-providers 2025-03-24 13:58:58 +07:00
e399fa8b68
Mark Boolean Provider register as internal 2025-03-23 12:34:05 +07:00
d55fbbe62f
Merge branch '1.21' into worldgen-placement-providers 2025-03-23 10:23:05 +07:00
b6b3cbed81
Merge branch '1.21' into worldgen-placement-providers 2025-03-22 00:38:45 +07:00
97c4922765
a 2025-03-11 18:26:50 +07:00
d2e7971726
Merge branch '1.21' into worldgen-placement-providers 2025-03-11 18:01:55 +07:00
e7f2e0551a
Merge branch '1.21' into worldgen-placement-providers 2025-03-09 22:01:00 +07:00
84ca0c2a24
Use GJRAND64 for enormous placement 2025-03-09 21:47:17 +07:00
326490b73c
Merge branch '1.21' into worldgen-placement-providers 2025-03-09 21:29:52 +07:00
ed04a8c4d2
Drastically reduce tritanium clusters generated 2025-03-09 15:39:20 +07:00
611f4055e9
Ellipsoid heart with worms tritanium placement 2025-03-09 15:38:20 +07:00
00ff8fa91e
damn 2025-03-09 15:01:23 +07:00
3ee539bc9d
Back to debug placement 2025-03-09 13:57:31 +07:00
44b0bcd776
Use nested caches for enormous placements instead of weak hash map
Should recycle memory better
2025-03-09 13:34:47 +07:00
cc456958e1
Remove duplicates from positions provided by enormous placement 2025-03-09 13:31:42 +07:00
5f76aa1661
AlwaysTrue and AlwaysFalse boolean providers 2025-03-09 13:06:46 +07:00
bd8c5ed97b
Clarify docs 2025-03-09 13:04:31 +07:00
674c875249
Make enormous placement a wrapper around regular placement modifiers, simplifying code 2025-03-09 12:59:36 +07:00
7736762c86
Some code for allowing multiple worms 2025-03-09 12:49:53 +07:00
274b5c059a
Fix AbstractEnormousPlacement doesnt separate caches for different dimensions 2025-03-09 12:42:28 +07:00
45d7b30b8a
As usual 2025-03-09 00:07:28 +07:00
abeeda8139
Reduce maximum amount of dilithium in vein, make tritanium worm vein rarer 2025-03-08 23:55:26 +07:00
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
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
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
157d2c5498
Merge branch '1.21' into worldgen-placement-providers 2025-03-08 14:12:12 +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
70ac0f2c7f
No longer require seedMix to be non-zero 2025-03-07 21:39:47 +07:00
0ef34a00cc
Merge branch '1.21' into worldgen-placement-providers 2025-03-07 21:31:07 +07:00
cbc95e8e5b
Merge branch '1.21' into worldgen-placement-providers 2025-03-05 10:52:04 +07:00
0c15c93dcd
Disallow to specify seed mix as zero to avoid EXTREMELY unlikely case where world's seed is zero, and we are generating chunk at 0, 0, in which Xoshiro256 will generate an exception 2025-03-05 10:25:17 +07:00
92846cbad0
Merge branch '1.21' into worldgen-placement-providers 2025-03-05 09:46:36 +07:00
ab42d1c1fb
Use Xoshiro256SSRandom instead of CMWC in enormous placements 2025-03-05 09:45:22 +07:00
b8468f7828
Merge branch 'prng' into worldgen-placement-providers 2025-03-05 09:44:23 +07:00
01a6ca27bf
Merge branch '1.21' into worldgen-placement-providers 2025-03-05 00:45:13 +07:00
db3f22f3fc
Merge branch '1.21' into worldgen-placement-providers 2025-03-04 18:51:27 +07:00
4da5ef81f7
Merge branch '1.21' into worldgen-placement-providers 2025-03-03 14:44:28 +07:00
536f69930c
Use CMWC random for enormous placements 2025-03-03 12:12:15 +07:00
988f078d24
Merge branch '1.21' into worldgen-placement-providers 2025-03-03 12:11:23 +07:00
f6abe0f038
Initial worm placement code along with debug feature 2025-03-03 06:40:29 +07:00
97d30d3a19
Add seed mix to enormous placements 2025-03-02 11:36:35 +07:00
cc9ebef018
Remove chunk cache tweaking from datapack 2025-03-02 11:29:08 +07:00
f9c8130e26
Merge branch '1.21' into worldgen-placement-providers 2025-03-02 11:25:40 +07:00
c0812687cb
OneOf number provider, improve dilithium ore generation further 2025-03-02 11:21:30 +07:00
cd38e1a154
Adjust dilithium placement parameters 2025-03-02 09:59:20 +07:00
a903b9cff8
Enormous placement modifier, greatly enhance dilithium ore placement 2025-03-02 09:56:58 +07:00
33 changed files with 1709 additions and 137 deletions

View File

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

View File

@ -7,14 +7,15 @@ 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.UniformFloat
import net.minecraft.util.valueproviders.UniformInt
import net.minecraft.world.level.levelgen.GenerationStep
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
@ -25,20 +26,40 @@ import net.minecraft.world.level.levelgen.structure.templatesystem.TagMatchTest
import net.neoforged.neoforge.common.world.BiomeModifier
import net.neoforged.neoforge.registries.NeoForgeRegistries
import ru.dbotthepony.mc.otm.util.math.Decimal
import ru.dbotthepony.mc.otm.data.world.EllipsoidPlacement
import ru.dbotthepony.mc.otm.data.world.StandardDeviationHeightProvider
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.server.world.placement.StandardDeviationHeightProvider
import ru.dbotthepony.mc.otm.registry.game.MBlocks
import ru.dbotthepony.mc.otm.registry.data.MWorldGenFeatures
import ru.dbotthepony.mc.otm.server.world.EnhancedPlacedFeature
import ru.dbotthepony.mc.otm.server.world.feature.BlackHolePlacerFeature
import ru.dbotthepony.mc.otm.server.world.feature.EnhancedFeature
import ru.dbotthepony.mc.otm.server.world.placement
import ru.dbotthepony.mc.otm.server.world.placement.EllipsoidPlacement
import ru.dbotthepony.mc.otm.server.world.placement.EnhancedChainPlacement
import ru.dbotthepony.mc.otm.server.world.placement.EnhancedCountPlacement
import ru.dbotthepony.mc.otm.server.world.placement.EnhancedPlacement
import ru.dbotthepony.mc.otm.server.world.placement.EnhancedSplitPlacement
import ru.dbotthepony.mc.otm.server.world.placement.WormPlacement
import ru.dbotthepony.mc.otm.server.world.wrap
private object ConfiguredFeatures {
val TRITANIUM_ORE = key("tritanium_ore")
val TRITANIUM_ORE_SMALL = key("tritanium_ore_small")
val DILITHIUM = key("dilithium")
val BLACK_HOLE = key("black_hole")
private fun key(name: String): ResourceKey<ConfiguredFeature<*, *>> {
return ResourceKey.create(Registries.CONFIGURED_FEATURE, modLocation(name))
}
private fun ekey(name: String): ResourceKey<EnhancedFeature.Configured<*, *>> {
return ResourceKey.create(MRegistries.CONFIGURED_FEATURE, modLocation(name))
}
}
fun registerEnhancedConfiguredFeatures(context: BootstrapContext<EnhancedFeature.Configured<*, *>>) {
}
fun registerConfiguredFeatures(context: BootstrapContext<ConfiguredFeature<*, *>>) {
@ -52,6 +73,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.REPLACE_SINGLE_BLOCK, ReplaceBlockConfiguration(target)))
//context.register(ConfiguredFeatures.TRITANIUM_ORE_SMALL, ConfiguredFeature(MWorldGenFeatures.DEBUG_PLACEMENT, DebugPlacerFeature.Config(MBlocks.TRITANIUM_ORE.defaultBlockState())))
}
run {
@ -60,7 +83,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(
@ -71,7 +94,7 @@ fun registerConfiguredFeatures(context: BootstrapContext<ConfiguredFeature<*, *>
private object PlacedFeatures {
val NORMAL_TRITANIUM = key("normal_tritanium")
val DEEP_TRITANIUM = key("deep_tritanium")
val CLOUD_TITANIUM = key("cloud_tritanium")
val WORM_TRITANIUM = key("worm_tritanium")
val DILITHIUM = key("dilithium")
val BLACK_HOLE = key("black_hole")
@ -82,6 +105,12 @@ private object PlacedFeatures {
fun registerPlacedFeatures(context: BootstrapContext<PlacedFeature>) {
val configured = context.lookup(Registries.CONFIGURED_FEATURE)
val econfigured = context.lookup(MRegistries.CONFIGURED_FEATURE)
val ringularity = OneOfFloatProvider.of(
ClampedNormalFloat.of(0.4f, 0.2f, -2f, 2f),
ClampedNormalFloat.of(-0.4f, 0.2f, -2f, 2f),
)
run {
val ore = configured.getOrThrow(ConfiguredFeatures.TRITANIUM_ORE)
@ -89,7 +118,7 @@ fun registerPlacedFeatures(context: BootstrapContext<PlacedFeature>) {
context.register(PlacedFeatures.NORMAL_TRITANIUM, PlacedFeature(
ore,
listOf(
CountPlacement.of(UniformInt.of(2, 6)),
CountPlacement.of(UniformInt.of(1, 3)),
InSquarePlacement.spread(),
HeightRangePlacement.of(StandardDeviationHeightProvider(VerticalAnchor.absolute(10), 15.0))
)
@ -98,53 +127,93 @@ fun registerPlacedFeatures(context: BootstrapContext<PlacedFeature>) {
context.register(PlacedFeatures.DEEP_TRITANIUM, PlacedFeature(
ore,
listOf(
CountPlacement.of(UniformInt.of(4, 8)),
CountPlacement.of(UniformInt.of(3, 5)),
InSquarePlacement.spread(),
HeightRangePlacement.of(VeryBiasedToBottomHeight.of(VerticalAnchor.aboveBottom(4), VerticalAnchor.absolute(0), 16))
)
))
context.register(PlacedFeatures.CLOUD_TITANIUM, PlacedFeature(
ore,
listOf(
RarityFilter.onAverageOnceEvery(16),
InSquarePlacement.spread(),
HeightRangePlacement.of(StandardDeviationHeightProvider(VerticalAnchor.absolute(10), 15.0)),
EllipsoidPlacement(
x = ClampedNormalInt.of(0f, 6f, Int.MIN_VALUE, Int.MAX_VALUE),
y = ClampedNormalInt.of(0f, 12f, Int.MIN_VALUE, Int.MAX_VALUE),
z = ClampedNormalInt.of(0f, 6f, Int.MIN_VALUE, Int.MAX_VALUE),
count = ClampedNormalInt.of(60f, 60f, 40, 160),
xLength = ClampedNormalFloat.of(11f, 4f, 6f, 14f),
yLength = ClampedNormalFloat.of(11f, 4f, 6f, 14f),
zLength = ClampedNormalFloat.of(11f, 4f, 6f, 14f),
context.register(
PlacedFeatures.WORM_TRITANIUM,
EnhancedPlacedFeature.configure(
chunkScanRange = 24,
seedMix = 9284343575495L,
root = EnhancedChainPlacement(
RarityFilter.onAverageOnceEvery(140).wrap(),
InSquarePlacement.spread().wrap(),
HeightRangePlacement.of(StandardDeviationHeightProvider(VerticalAnchor.absolute(-40), 15.0)).wrap(),
EnhancedSplitPlacement(
EnhancedSplitPlacement.Mode.COMBINE,
// "heart"
EllipsoidPlacement(
count = UniformInt.of(600, 900),
xLength = UniformFloat.of(9f, 12f),
yLength = UniformFloat.of(9f, 12f),
zLength = UniformFloat.of(9f, 12f),
x = ringularity,
y = ringularity,
z = ringularity,
),
// "branches"
EnhancedChainPlacement(
EnhancedCountPlacement(UniformInt.of(2, 7)),
WormPlacement(
length = UniformInt.of(60, 120),
turnRateXY = WormPlacement.normalDistributedTurnRate(2f, 3f),
initialAngleXY = WormPlacement.normalDistributedTurnRate(3f, 4f),
turnRateXZ = WormPlacement.normalDistributedTurnRate(30f),
turnSpeedXZ = WormPlacement.constantTurnRate(10f),
turnSpeedXY = WormPlacement.constantTurnRate(4f),
turnChanceXY = BooleanProvider.Unbiased(6),
turnChanceXZ = BooleanProvider.BiasedLinear(0.6f, 5),
maxTravelUp = 90,
maxTravelDown = 10,
),
EllipsoidPlacement(
count = UniformInt.of(3, 6),
xLength = ConstantFloat.of(4f),
yLength = ConstantFloat.of(4f),
zLength = ConstantFloat.of(4f),
x = ringularity,
y = ringularity,
z = ringularity,
)
)
),
configured.getOrThrow(ConfiguredFeatures.TRITANIUM_ORE_SMALL).placement()
)
)
))
)
}
run {
val ore = configured.getOrThrow(ConfiguredFeatures.DILITHIUM)
context.register(PlacedFeatures.DILITHIUM, PlacedFeature(
ore,
listOf(
RarityFilter.onAverageOnceEvery(12),
InSquarePlacement.spread(),
HeightRangePlacement.of(StandardDeviationHeightProvider(VerticalAnchor.absolute(0), 15.0)),
EllipsoidPlacement(
x = ClampedNormalInt.of(0f, 8f, Int.MIN_VALUE, Int.MAX_VALUE),
y = ClampedNormalInt.of(0f, 20f, Int.MIN_VALUE, Int.MAX_VALUE),
z = ClampedNormalInt.of(0f, 8f, Int.MIN_VALUE, Int.MAX_VALUE),
count = ClampedNormalInt.of(200f, 200f, 200, 600),
xLength = ClampedNormalFloat.of(11f, 4f, 8f, 14f),
// allow crystals to generate as far as standard deviation allows
// to increase chance for player to discover crystal vein
yLength = ConstantFloat.of(60f),
zLength = ClampedNormalFloat.of(11f, 4f, 8f, 14f),
context.register(
PlacedFeatures.DILITHIUM,
EnhancedPlacedFeature.configure(
chunkScanRange = 6,
seedMix = 237483209523709234L,
root = EnhancedChainPlacement(
RarityFilter.onAverageOnceEvery(120).wrap(),
InSquarePlacement.spread().wrap(),
HeightRangePlacement.of(StandardDeviationHeightProvider(VerticalAnchor.absolute(0), 15.0)).wrap(),
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),
rotateXZ = BooleanProvider.AlwaysTrue,
),
ore.placement()
)
)
))
)
}
val blackHole = configured.getOrThrow(ConfiguredFeatures.BLACK_HOLE)
@ -179,7 +248,7 @@ fun registerBiomeModifiers(context: BootstrapContext<BiomeModifier>) {
HolderSet.direct(
placed.getOrThrow(PlacedFeatures.NORMAL_TRITANIUM),
placed.getOrThrow(PlacedFeatures.DEEP_TRITANIUM),
placed.getOrThrow(PlacedFeatures.CLOUD_TITANIUM),
placed.getOrThrow(PlacedFeatures.WORM_TRITANIUM),
placed.getOrThrow(PlacedFeatures.DILITHIUM),
),
GenerationStep.Decoration.UNDERGROUND_ORES

View File

@ -6,10 +6,8 @@ import net.neoforged.bus.api.EventPriority
import net.neoforged.fml.common.Mod
import net.neoforged.fml.event.lifecycle.FMLClientSetupEvent
import net.neoforged.fml.event.lifecycle.FMLCommonSetupEvent
import ru.dbotthepony.mc.otm.player.android.AndroidResearchDescription
import ru.dbotthepony.mc.otm.player.android.AndroidResearchDescriptions
import ru.dbotthepony.mc.otm.player.android.AndroidResearchManager
import ru.dbotthepony.mc.otm.player.android.AndroidResearchResult
import ru.dbotthepony.mc.otm.player.android.AndroidResearchResults
import ru.dbotthepony.mc.otm.player.android.feature.EnderTeleporterFeature
import ru.dbotthepony.mc.otm.block.entity.MatteryBlockEntity
@ -56,6 +54,7 @@ import ru.dbotthepony.mc.otm.config.ServerConfig
import ru.dbotthepony.mc.otm.config.ToolsConfig
import ru.dbotthepony.mc.otm.container.ItemFilter
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
@ -89,11 +88,14 @@ import ru.dbotthepony.mc.otm.registry.data.MHeightProviders
import ru.dbotthepony.mc.otm.registry.data.MItemFunctionTypes
import ru.dbotthepony.mc.otm.registry.data.MLootItemConditions
import ru.dbotthepony.mc.otm.registry.data.MLootNumberProviders
import ru.dbotthepony.mc.otm.registry.data.MNumberProviders
import ru.dbotthepony.mc.otm.registry.data.MPlacementModifiers
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.server.triggers.KillAsAndroidTrigger
import ru.dbotthepony.mc.otm.server.world.feature.EnhancedFeature
import ru.dbotthepony.mc.otm.server.world.placement.EnhancedPlacement
import thedarkcolour.kotlinforforge.neoforge.forge.DIST
import thedarkcolour.kotlinforforge.neoforge.forge.FORGE_BUS
import thedarkcolour.kotlinforforge.neoforge.forge.LOADING_CONTEXT
@ -129,6 +131,7 @@ object OverdriveThatMatters {
CommandArgumentTypes.register(MOD_BUS)
MHeightProviders.register(MOD_BUS)
MPlacementModifiers.register(MOD_BUS)
MNumberProviders.register(MOD_BUS)
MLootNumberProviders.register(MOD_BUS)
StorageStack.register(MOD_BUS)
@ -139,6 +142,9 @@ object OverdriveThatMatters {
MOD_BUS.addListener(::registerNetworkPackets)
DecimalProvider.register(MOD_BUS)
BooleanProvider.register(MOD_BUS)
EnhancedFeature.register(MOD_BUS)
EnhancedPlacement.register(MOD_BUS)
AndroidResearchDescriptions.register(MOD_BUS)
AndroidResearchResults.register(MOD_BUS)

View File

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

View File

@ -0,0 +1,142 @@
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.neoforged.bus.api.IEventBus
import ru.dbotthepony.mc.otm.data.codec.inRange
import ru.dbotthepony.mc.otm.registry.MBuiltInRegistries
import ru.dbotthepony.mc.otm.registry.MDeferredRegister
import ru.dbotthepony.mc.otm.registry.MRegistries
interface BooleanProvider {
interface Type<T : BooleanProvider> {
val codec: MapCodec<T>
}
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
*/
data 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.
*/
data 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)
}
}
}
object AlwaysTrue : BooleanProvider, Instance, Type<AlwaysTrue> {
override fun instance(): Instance {
return this
}
override fun sample(random: RandomSource): Boolean {
return true
}
override val type: Type<*>
get() = this
override val codec: MapCodec<AlwaysTrue> = MapCodec.unit(this)
}
object AlwaysFalse : BooleanProvider, Instance, Type<AlwaysFalse> {
override fun instance(): Instance {
return this
}
override fun sample(random: RandomSource): Boolean {
return false
}
override val type: Type<*>
get() = this
override val codec: MapCodec<AlwaysFalse> = MapCodec.unit(this)
}
companion object {
private val registrar = MDeferredRegister(MRegistries.BOOLEAN_PROVIDER)
init {
registrar.register("unbiased") { Unbiased.Companion }
registrar.register("linear_bias") { BiasedLinear.Companion }
registrar.register("true") { AlwaysTrue }
registrar.register("false") { AlwaysFalse }
}
val CODEC: Codec<BooleanProvider> by lazy {
MBuiltInRegistries.BOOLEAN_PROVIDER.byNameCodec().dispatch({ it.type }, { it.codec })
}
internal fun register(bus: IEventBus) {
registrar.register(bus)
}
}
}

View File

@ -1,92 +0,0 @@
package ru.dbotthepony.mc.otm.data.world
import com.mojang.serialization.MapCodec
import com.mojang.serialization.codecs.RecordCodecBuilder
import net.minecraft.core.BlockPos
import net.minecraft.util.RandomSource
import net.minecraft.util.valueproviders.FloatProvider
import net.minecraft.util.valueproviders.IntProvider
import net.minecraft.world.level.levelgen.placement.PlacementContext
import net.minecraft.world.level.levelgen.placement.PlacementModifier
import net.minecraft.world.level.levelgen.placement.PlacementModifierType
import ru.dbotthepony.mc.otm.util.math.component1
import ru.dbotthepony.mc.otm.util.math.component2
import ru.dbotthepony.mc.otm.util.math.component3
import ru.dbotthepony.mc.otm.util.math.minus
import ru.dbotthepony.mc.otm.util.math.plus
import ru.dbotthepony.mc.otm.registry.data.MPlacementModifiers
import java.util.stream.Stream
import kotlin.math.PI
import kotlin.math.roundToInt
// aka "cloud placement"
data class EllipsoidPlacement(
val x: IntProvider,
val z: IntProvider,
val y: IntProvider,
val count: IntProvider,
val xLength: FloatProvider,
val zLength: FloatProvider,
val yLength: FloatProvider,
) : PlacementModifier() {
init {
require(xLength.minValue >= 1f) { "Bad ellipsoid x minimal size: $xLength" }
require(zLength.minValue >= 1f) { "Bad ellipsoid z minimal size: $zLength" }
require(yLength.minValue >= 1f) { "Bad ellipsoid y minimal size: $yLength" }
}
override fun getPositions(
context: PlacementContext,
random: RandomSource,
position: BlockPos
): Stream<BlockPos> {
var count = count.sample(random)
if (count <= 0) {
return Stream.empty()
}
val xLength = xLength.sample(random)
val zLength = zLength.sample(random)
val yLength = yLength.sample(random)
val xPow = xLength * xLength
val zPow = zLength * zLength
val yPow = yLength * yLength
count = minOf(count, (xLength * zLength * yLength * PI * (4.0 / 3.0)).roundToInt())
count = 600
return Stream.generate { position + BlockPos(this.x.sample(random), this.y.sample(random), this.z.sample(random)) }
.limit(count * 10L) // failsafe
.filter {
val (ellipsoidX, ellipsoidY, ellipsoidZ) = it - position
(ellipsoidX * ellipsoidX) / xPow +
(ellipsoidY * ellipsoidY) / yPow +
(ellipsoidZ * ellipsoidZ) / zPow <= 1.0f
}
.distinct()
.limit(count.toLong())
}
override fun type(): PlacementModifierType<*> {
return MPlacementModifiers.ELLIPSOID_PLACEMENT
}
companion object {
val CODEC: MapCodec<EllipsoidPlacement> by lazy {
RecordCodecBuilder.mapCodec {
it.group(
IntProvider.CODEC.fieldOf("x").forGetter(EllipsoidPlacement::x),
IntProvider.CODEC.fieldOf("y").forGetter(EllipsoidPlacement::y),
IntProvider.CODEC.fieldOf("z").forGetter(EllipsoidPlacement::z),
IntProvider.CODEC.fieldOf("count").forGetter(EllipsoidPlacement::count),
FloatProvider.codec(1f, Float.MAX_VALUE).fieldOf("x_size").forGetter(EllipsoidPlacement::xLength),
FloatProvider.codec(1f, Float.MAX_VALUE).fieldOf("z_size").forGetter(EllipsoidPlacement::zLength),
FloatProvider.codec(1f, Float.MAX_VALUE).fieldOf("y_size").forGetter(EllipsoidPlacement::yLength),
).apply(it, ::EllipsoidPlacement)
}
}
}
}

View File

@ -0,0 +1,38 @@
package ru.dbotthepony.mc.otm.data.world
import net.minecraft.util.RandomSource
import net.minecraft.util.valueproviders.FloatProvider
import net.minecraft.util.valueproviders.FloatProviderType
import ru.dbotthepony.mc.otm.registry.data.MNumberProviders
class OneOfFloatProvider(private val provider: OneOfProvider<FloatProvider>) : FloatProvider() {
override fun sample(random: RandomSource): Float {
return provider.select(random).sample(random)
}
override fun getMinValue(): Float {
return provider.children.minOf { it.minValue }
}
override fun getMaxValue(): Float {
return provider.children.minOf { it.maxValue }
}
override fun getType(): FloatProviderType<*> {
return MNumberProviders.ONE_OF_FLOAT
}
companion object {
@JvmStatic
fun of(vararg providers: FloatProvider): OneOfFloatProvider {
return OneOfFloatProvider(OneOfProvider.of(*providers))
}
@JvmStatic
fun of(providers: Collection<FloatProvider>): OneOfFloatProvider {
return OneOfFloatProvider(OneOfProvider.of(providers))
}
val CODEC = OneOfProvider.createCodec(FloatProvider.CODEC, ::OneOfFloatProvider, OneOfFloatProvider::provider)
}
}

View File

@ -0,0 +1,38 @@
package ru.dbotthepony.mc.otm.data.world
import net.minecraft.util.RandomSource
import net.minecraft.util.valueproviders.IntProvider
import net.minecraft.util.valueproviders.IntProviderType
import ru.dbotthepony.mc.otm.registry.data.MNumberProviders
class OneOfIntProvider(private val provider: OneOfProvider<IntProvider>) : IntProvider() {
override fun sample(random: RandomSource): Int {
return provider.select(random).sample(random)
}
override fun getMinValue(): Int {
return provider.children.minOf { it.minValue }
}
override fun getMaxValue(): Int {
return provider.children.minOf { it.maxValue }
}
override fun getType(): IntProviderType<*> {
return MNumberProviders.ONE_OF_INT
}
companion object {
@JvmStatic
fun of(vararg providers: IntProvider): OneOfIntProvider {
return OneOfIntProvider(OneOfProvider.of(*providers))
}
@JvmStatic
fun of(providers: Collection<IntProvider>): OneOfIntProvider {
return OneOfIntProvider(OneOfProvider.of(providers))
}
val CODEC = OneOfProvider.createCodec(IntProvider.CODEC, ::OneOfIntProvider, OneOfIntProvider::provider)
}
}

View File

@ -0,0 +1,85 @@
package ru.dbotthepony.mc.otm.data.world
import com.google.common.collect.ImmutableList
import com.mojang.serialization.Codec
import com.mojang.serialization.DataResult
import com.mojang.serialization.MapCodec
import com.mojang.serialization.codecs.RecordCodecBuilder
import net.minecraft.util.RandomSource
import net.minecraft.util.valueproviders.IntProvider
import ru.dbotthepony.mc.otm.util.random
import java.util.Optional
class OneOfProvider<T> private constructor(val children: List<T>, val selector: Optional<IntProvider> = Optional.empty()) {
fun select(random: RandomSource): T {
if (selector.isEmpty) {
return children.random(random)
}
val index = selector.get().sample(random)
if (index !in children.indices) {
throw RuntimeException("Provided selector ($selector) provided index out of bounds: $index (bounds: ${children.indices})")
}
return children[index]
}
class Builder<T> {
private var selector: IntProvider? = null
private val inputs = ArrayList<T>()
fun indexSelector(selector: IntProvider): Builder<T> {
this.selector = selector
return this
}
fun add(vararg provider: T): Builder<T> {
inputs.addAll(provider)
return this
}
fun copy(): Builder<T> {
val new = Builder<T>()
new.selector = selector
new.inputs.addAll(inputs)
return new
}
fun build(): OneOfProvider<T> {
return OneOfProvider(ImmutableList.copyOf(inputs), Optional.ofNullable(selector))
}
}
companion object {
@JvmStatic
fun <T> of(vararg providers: T): OneOfProvider<T> {
return OneOfProvider(ImmutableList.copyOf(providers))
}
@JvmStatic
fun <T> of(providers: Collection<T>): OneOfProvider<T> {
return OneOfProvider(ImmutableList.copyOf(providers))
}
@JvmStatic
fun <T> createCodec(childrenCodec: Codec<T>): MapCodec<OneOfProvider<T>> {
return RecordCodecBuilder.mapCodec {
it.group(
Codec.list(childrenCodec, 1, Int.MAX_VALUE).fieldOf("children").forGetter<OneOfProvider<T>> { it.children },
IntProvider.POSITIVE_CODEC.optionalFieldOf("selector").forGetter<OneOfProvider<T>> { it.selector },
).apply(it, ::OneOfProvider)
}.validate {
if (it.selector.isEmpty || it.selector.get().minValue in it.children.indices && it.selector.get().maxValue in it.children.indices) {
return@validate DataResult.success(it)
} else {
return@validate DataResult.error { "Provided number sampler ${it.selector.get()} can produce values outside of children list index range (list size: ${it.children.size})" }
}
}
}
fun <S, T> createCodec(childrenCodec: Codec<T>, to: (OneOfProvider<T>) -> S, from: (S) -> OneOfProvider<T>): MapCodec<S> {
return createCodec(childrenCodec).xmap(to, from)
}
}
}

View File

@ -51,6 +51,15 @@ object MBuiltInRegistries {
val ANDROID_FEATURE by Delegate(MRegistries.ANDROID_FEATURE) { sync(true) }
val STACK_TYPE by Delegate(MRegistries.STACK_TYPE)
val BOOLEAN_PROVIDER by Delegate(MRegistries.BOOLEAN_PROVIDER) {
defaultKey(ResourceLocation(OverdriveThatMatters.MOD_ID, "false"))
}
val FEATURE by Delegate(MRegistries.FEATURE)
val PLACEMENT_MODIFIER by Delegate(MRegistries.PLACEMENT_MODIFIER) {
defaultKey(ResourceLocation(OverdriveThatMatters.MOD_ID, "passthrough"))
}
val ITEM_FILTER by Delegate(MRegistries.ITEM_FILTER) {
defaultKey(ResourceLocation(OverdriveThatMatters.MOD_ID, "empty"))
}

View File

@ -5,6 +5,7 @@ import net.minecraft.resources.ResourceKey
import ru.dbotthepony.mc.otm.OverdriveThatMatters
import ru.dbotthepony.mc.otm.container.ItemFilter
import ru.dbotthepony.mc.otm.util.ResourceLocation
import ru.dbotthepony.mc.otm.data.world.BooleanProvider
import ru.dbotthepony.mc.otm.data.world.DecimalProvider
import ru.dbotthepony.mc.otm.matter.AbstractRegistryAction
import ru.dbotthepony.mc.otm.matter.IMatterFunction
@ -13,6 +14,8 @@ 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.server.world.feature.EnhancedFeature
import ru.dbotthepony.mc.otm.server.world.placement.EnhancedPlacement
object MRegistries {
private fun <T> k(name: String): ResourceKey<Registry<T>> {
@ -27,5 +30,9 @@ object MRegistries {
val ANDROID_RESEARCH_RESULT = k<AndroidResearchResult.Type<*>>("android_research_result")
val ANDROID_FEATURE = k<AndroidFeatureType<*>>("android_feature")
val STACK_TYPE = k<StorageStack.Type<*>>("stack_type")
val BOOLEAN_PROVIDER = k<BooleanProvider.Type<*>>("boolean_provider")
val FEATURE = k<EnhancedFeature<*>>("feature")
val CONFIGURED_FEATURE = k<EnhancedFeature.Configured<*, *>>("configured_feature")
val PLACEMENT_MODIFIER = k<EnhancedPlacement.Type<*>>("placement_modifier")
val ITEM_FILTER = k<ItemFilter.Type<*>>("item_filter")
}

View File

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

View File

@ -0,0 +1,22 @@
package ru.dbotthepony.mc.otm.registry.data
import net.minecraft.core.registries.BuiltInRegistries
import net.minecraft.util.valueproviders.FloatProviderType
import net.minecraft.util.valueproviders.IntProviderType
import net.neoforged.bus.api.IEventBus
import ru.dbotthepony.mc.otm.data.world.OneOfFloatProvider
import ru.dbotthepony.mc.otm.data.world.OneOfIntProvider
import ru.dbotthepony.mc.otm.registry.MDeferredRegister
object MNumberProviders {
private val floats = MDeferredRegister(BuiltInRegistries.FLOAT_PROVIDER_TYPE)
private val ints = MDeferredRegister(BuiltInRegistries.INT_PROVIDER_TYPE)
fun register(bus: IEventBus) {
floats.register(bus)
ints.register(bus)
}
val ONE_OF_FLOAT by floats.register("one_of_float") { FloatProviderType { OneOfFloatProvider.CODEC } }
val ONE_OF_INT by ints.register("one_of_int") { IntProviderType { OneOfIntProvider.CODEC } }
}

View File

@ -3,15 +3,37 @@ package ru.dbotthepony.mc.otm.registry.data
import net.minecraft.core.registries.BuiltInRegistries
import net.minecraft.world.level.levelgen.placement.PlacementModifierType
import net.neoforged.bus.api.IEventBus
import ru.dbotthepony.mc.otm.data.world.EllipsoidPlacement
import ru.dbotthepony.mc.otm.registry.MDeferredRegister
import ru.dbotthepony.mc.otm.registry.MRegistries
import ru.dbotthepony.mc.otm.server.world.feature.EnhancedFeature
import ru.dbotthepony.mc.otm.server.world.placement.ChainPlacement
import ru.dbotthepony.mc.otm.server.world.placement.EllipsoidPlacement
import ru.dbotthepony.mc.otm.server.world.placement.EnhancedChainPlacement
import ru.dbotthepony.mc.otm.server.world.placement.EnhancedCountPlacement
import ru.dbotthepony.mc.otm.server.world.placement.EnhancedSplitPlacement
import ru.dbotthepony.mc.otm.server.world.placement.SplitPlacement
import ru.dbotthepony.mc.otm.server.world.placement.WormPlacement
object MPlacementModifiers {
private val registry = MDeferredRegister(BuiltInRegistries.PLACEMENT_MODIFIER_TYPE)
private val enhancedRegistry = MDeferredRegister(MRegistries.PLACEMENT_MODIFIER)
internal fun register(bus: IEventBus) {
registry.register(bus)
enhancedRegistry.register(bus)
}
val ELLIPSOID_PLACEMENT by registry.register("ellipsoid") { PlacementModifierType { EllipsoidPlacement.CODEC } }
val WORM_PLACEMENT by registry.register("worm") { PlacementModifierType { WormPlacement.CODEC } }
val SPLIT by registry.register("split") { PlacementModifierType { SplitPlacement.CODEC} }
val CHAIN by registry.register("chain") { PlacementModifierType { ChainPlacement.CODEC } }
init {
enhancedRegistry.register("feature") { EnhancedFeature.ConfiguredWrapper.Companion }
enhancedRegistry.register("ellipsoid") { EllipsoidPlacement.Companion }
enhancedRegistry.register("worm") { WormPlacement.Companion }
enhancedRegistry.register("split") { EnhancedSplitPlacement.Companion }
enhancedRegistry.register("chain") { EnhancedChainPlacement.Companion }
enhancedRegistry.register("count") { EnhancedCountPlacement.Companion }
}
}

View File

@ -2,8 +2,11 @@ package ru.dbotthepony.mc.otm.registry.data
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.server.world.EnhancedPlacedFeature
import ru.dbotthepony.mc.otm.server.world.feature.BlackHolePlacerFeature
import ru.dbotthepony.mc.otm.server.world.feature.DebugPlacerFeature
object MWorldGenFeatures {
private val registry = MDeferredRegister(BuiltInRegistries.FEATURE)
@ -13,4 +16,6 @@ object MWorldGenFeatures {
}
val BLACK_HOLE_PLACER by registry.register("black_hole_placer") { BlackHolePlacerFeature }
val DEBUG_PLACEMENT by registry.register("debug") { DebugPlacerFeature }
val ENHANCED_FEATURE by registry.register("enhanced_feature") { EnhancedPlacedFeature }
}

View File

@ -0,0 +1,180 @@
package ru.dbotthepony.mc.otm.server.world
import com.github.benmanes.caffeine.cache.Cache
import com.github.benmanes.caffeine.cache.Caffeine
import com.github.benmanes.caffeine.cache.Scheduler
import com.mojang.serialization.Codec
import com.mojang.serialization.codecs.RecordCodecBuilder
import it.unimi.dsi.fastutil.io.FastByteArrayOutputStream
import it.unimi.dsi.fastutil.objects.ObjectArrayList
import it.unimi.dsi.fastutil.objects.ObjectRBTreeSet
import net.minecraft.Util
import net.minecraft.core.BlockPos
import net.minecraft.core.Holder
import net.minecraft.core.SectionPos
import net.minecraft.server.level.ServerLevel
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 ru.dbotthepony.kommons.util.XXHash64
import ru.dbotthepony.mc.otm.THREAD_LOCAL_RANDOM
import ru.dbotthepony.mc.otm.data.codec.minRange
import ru.dbotthepony.mc.otm.util.GJRAND64RandomSource
import ru.dbotthepony.mc.otm.util.shuffle
import ru.dbotthepony.mc.otm.server.world.feature.EnhancedFeature
import ru.dbotthepony.mc.otm.server.world.placement.EnhancedPlacement
import java.io.DataOutputStream
import java.time.Duration
import java.util.*
import kotlin.collections.ArrayList
import kotlin.math.sqrt
object EnhancedPlacedFeature : Feature<EnhancedPlacedFeature.Config>(
RecordCodecBuilder.create {
it.group(
Codec.INT.minRange(0).fieldOf("chunk_scan_range").forGetter(Config::chunkScanRange),
Codec.LONG.fieldOf("seed_mix").forGetter(Config::seedMix),
EnhancedPlacement.CODEC.fieldOf("root").forGetter(Config::root),
).apply(it, ::Config)
}
) {
private val emptyVariableMap = PlacementVariableMap()
fun configure(
chunkScanRange: Int,
seedMix: Long,
root: EnhancedPlacement
): PlacedFeature {
return PlacedFeature(Holder.direct(ConfiguredFeature(EnhancedPlacedFeature, Config(chunkScanRange, seedMix, root))), listOf())
}
class Config(
val chunkScanRange: Int,
val seedMix: Long,
val root: EnhancedPlacement,
) : FeatureConfiguration {
private class GeneratedChunk : EnhancedPlacementContext.Placer {
private data class Placement(val context: EnhancedPlacementContext, val positions: TreeSet<PlacementPos>, val feature: EnhancedFeature.Configured<*, *>)
private val placed = LinkedList<Placement>()
override fun place(context: EnhancedPlacementContext, positions: List<PlacementPos>, feature: EnhancedFeature.Configured<*, *>) {
if (positions.isNotEmpty()) {
placed.add(Placement(context, TreeSet<PlacementPos>().also { it.addAll(positions) }, feature))
}
}
fun place(context: FeaturePlaceContext<*>): Boolean {
var any = false
val pos = ChunkPos(context.origin())
for ((eContext, positions, feature) in placed) {
val itr = positions.tailSet(PlacementPos(BlockPos(pos.minBlockX, Int.MIN_VALUE, pos.minBlockZ), emptyVariableMap)).iterator()
if (!itr.hasNext())
continue
val result = TreeSet<PlacementPos>()
for (placementPos in itr) {
if (SectionPos.blockToSectionCoord(placementPos.pos.x) != pos.x || SectionPos.blockToSectionCoord(placementPos.pos.z) != pos.z)
break
result.add(placementPos)
}
if (result.isNotEmpty()) {
any = feature.place(eContext.push(context.level()), result, positions) || any
}
}
return any
}
}
private val level2cache = Caffeine.newBuilder()
.scheduler(Scheduler.systemScheduler())
.executor(Util.backgroundExecutor())
.expireAfterAccess(Duration.ofMinutes(10))
.weakKeys()
.build<ServerLevel, Cache<ChunkPos, GeneratedChunk>>()
private fun getCache(level: WorldGenLevel): Cache<ChunkPos, GeneratedChunk> {
return level2cache.get(level.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, listOf(PlacementPos(BlockPos(chunkPos.minBlockX, 0, chunkPos.minBlockZ), PlacementVariableMap())))
return chunk
}
fun place(context: FeaturePlaceContext<*>): Boolean {
val cache = getCache(context.level())
val chunkPos = ChunkPos(context.origin())
val chunkPositions = ArrayList<ChunkPos>()
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)
chunkPositions.add(thisPos)
}
}
}
val withIndex = ObjectArrayList(chunkPositions.withIndex().iterator())
// evaluate chunks in random order, so in multithreaded environments we experience less congestion
// between threads which lookup cache while it is being populated on neighbouring chunks
withIndex.shuffle(THREAD_LOCAL_RANDOM)
data class P(val i: Int, val v: GeneratedChunk) : Comparable<P> {
override fun compareTo(other: P): Int {
return i.compareTo(other.i)
}
}
val instances = ArrayList<P>()
withIndex.forEach { (i, it) ->
instances.add(P(i, cache.get(it) { evaluate(context, it) }))
}
instances.sort()
var any = false
instances.forEach { any = it.v.place(context) }
return any
}
}
override fun place(context: FeaturePlaceContext<Config>): Boolean {
return context.config().place(context)
}
}

View File

@ -0,0 +1,89 @@
package ru.dbotthepony.mc.otm.server.world
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.mc.otm.server.world.feature.EnhancedFeature
import java.util.*
class EnhancedPlacementContext {
fun interface Placer {
fun place(context: EnhancedPlacementContext, positions: List<PlacementPos>, feature: EnhancedFeature.Configured<*, *>)
}
private data class SharedState(
val level: WorldGenLevel,
val generator: ChunkGenerator,
val random: RandomSource,
val origin: ChunkPos,
val placer: Placer,
val vanillaContext: PlacementContext,
)
private val state: SharedState
val level: WorldGenLevel
get() = state.level
val generator: ChunkGenerator
get() = state.generator
val random: RandomSource
get() = state.random
val origin: ChunkPos
get() = state.origin
private val placer: Placer
get() = state.placer
val vanillaContext: PlacementContext
get() = state.vanillaContext
val variables: PlacementVariableMap
constructor(
level: WorldGenLevel,
generator: ChunkGenerator,
random: RandomSource,
origin: ChunkPos,
placer: Placer,
) {
state = SharedState(
level = level,
generator = generator,
random = random,
origin = origin,
placer = placer,
vanillaContext = PlacementContext(level, generator, Optional.empty())
)
variables = PlacementVariableMap()
}
private constructor(parent: EnhancedPlacementContext) {
this.state = parent.state
this.variables = parent.variables.push()
}
private constructor(parent: EnhancedPlacementContext, context: WorldGenLevel) {
this.state = parent.state.copy(level = context)
this.variables = parent.variables.push()
}
fun push(): EnhancedPlacementContext {
return EnhancedPlacementContext(this)
}
fun place(positions: List<PlacementPos>, feature: EnhancedFeature.Configured<*, *>) {
placer.place(this, positions, feature)
}
fun <FC : FeatureConfiguration> vanillaFeatureContext(config: FC, position: BlockPos): FeaturePlaceContext<FC> {
return FeaturePlaceContext(Optional.empty(), level, generator, random, position, config)
}
fun push(context: WorldGenLevel): EnhancedPlacementContext {
return EnhancedPlacementContext(this, context)
}
}

View File

@ -0,0 +1,25 @@
package ru.dbotthepony.mc.otm.server.world
import net.minecraft.core.BlockPos
import net.minecraft.core.Holder
import net.minecraft.world.level.levelgen.feature.ConfiguredFeature
import net.minecraft.world.level.levelgen.placement.PlacementModifier
import ru.dbotthepony.mc.otm.util.collect.Vec3iHashStrategy
import ru.dbotthepony.mc.otm.server.world.feature.EnhancedFeature
import ru.dbotthepony.mc.otm.server.world.placement.EnhancedPlacement
fun PlacementModifier.wrap(): EnhancedPlacement {
return EnhancedPlacement.Wrapper(this)
}
data class PlacementPos(val pos: BlockPos, val variables: PlacementVariableMap) : Comparable<PlacementPos> {
override fun compareTo(other: PlacementPos): Int {
return Vec3iHashStrategy.compare(pos, other.pos)
}
}
@JvmName("placement")
fun Holder<EnhancedFeature.Configured<*, *>>.placement() = EnhancedFeature.ConfiguredWrapper(this)
@JvmName("vanillaPlacement")
fun Holder<ConfiguredFeature<*, *>>.placement() = EnhancedFeature.Wrapper.configure(this)
fun ConfiguredFeature<*, *>.placement() = EnhancedFeature.Wrapper.configure(this)

View File

@ -0,0 +1,9 @@
package ru.dbotthepony.mc.otm.server.world
import net.minecraft.resources.ResourceLocation
class PlacementVariable<T>(val name: ResourceLocation) : Comparable<PlacementVariable<T>> {
override fun compareTo(other: PlacementVariable<T>): Int {
return name.compareTo(other.name)
}
}

View File

@ -0,0 +1,56 @@
package ru.dbotthepony.mc.otm.server.world
import it.unimi.dsi.fastutil.objects.Object2ObjectAVLTreeMap
import ru.dbotthepony.kommons.util.KOptional
/**
* This is a "map stack" of sorts, and when you want to push some data to it
* you need to push new map to stack (by calling [push]) and then write new data to
* the map returned by latter.
*/
class PlacementVariableMap {
private val parent: PlacementVariableMap?
private val variables = Object2ObjectAVLTreeMap<PlacementVariable<*>, KOptional<*>>()
constructor() {
parent = null
}
private constructor(parent: PlacementVariableMap) {
this.parent = parent
}
private fun <T> recursiveGet(index: PlacementVariable<T>): KOptional<T>? {
return variables[index] as KOptional<T>? ?: parent?.recursiveGet(index)
}
operator fun <T> get(index: PlacementVariable<T>): KOptional<T> {
return recursiveGet(index) ?: KOptional()
}
operator fun <T> set(index: PlacementVariable<T>, value: T): PlacementVariableMap {
variables[index] = KOptional(value)
return this
}
fun <T> remove(index: PlacementVariable<T>): KOptional<T> {
val old = variables.put(index, KOptional<T>())
return old as KOptional<T>? ?: KOptional()
}
fun remove(index: Collection<PlacementVariable<*>>) {
index.forEach { remove(it) }
}
fun push(): PlacementVariableMap {
return PlacementVariableMap(this)
}
override fun equals(other: Any?): Boolean {
return other === this || other is PlacementVariableMap && variables == other.variables && parent == other.parent
}
override fun hashCode(): Int {
return parent.hashCode() * 31 + variables.hashCode() + 5
}
}

View File

@ -0,0 +1,16 @@
package ru.dbotthepony.mc.otm.server.world.feature
import net.minecraft.world.level.block.Block
import net.minecraft.world.level.block.state.BlockState
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 ru.dbotthepony.mc.otm.registry.game.MBlocks
object DebugPlacerFeature : Feature<DebugPlacerFeature.Config>(BlockState.CODEC.xmap(::Config, Config::blockState)) {
data class Config(val blockState: BlockState) : FeatureConfiguration
override fun place(context: FeaturePlaceContext<Config>): Boolean {
return context.level().setBlock(context.origin(), context.config().blockState, Block.UPDATE_CLIENTS)
}
}

View File

@ -0,0 +1,89 @@
package ru.dbotthepony.mc.otm.server.world.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.server.world.EnhancedPlacementContext
import ru.dbotthepony.mc.otm.server.world.PlacementPos
import ru.dbotthepony.mc.otm.server.world.placement.EnhancedPlacement
abstract class EnhancedFeature<FC>(codec: Codec<FC>) {
abstract fun place(context: EnhancedPlacementContext, config: FC, positions: Set<PlacementPos>, allPositions: Set<PlacementPos>): Boolean
data class Configured<F : EnhancedFeature<FC>, FC>(val feature: F, val config: FC) {
fun place(context: EnhancedPlacementContext, positions: Set<PlacementPos>, allPositions: Set<PlacementPos>): Boolean {
return feature.place(context, config, positions, allPositions)
}
fun placement(): ConfiguredWrapper {
return ConfiguredWrapper(Holder.direct(this))
}
}
data class ConfiguredWrapper(val configured: Holder<Configured<*, *>>) : EnhancedPlacement {
override fun evaluate(context: EnhancedPlacementContext, positions: List<PlacementPos>): List<PlacementPos> {
context.place(positions, configured.value())
return positions
}
override val type: EnhancedPlacement.Type<*>
get() = Companion
companion object : EnhancedPlacement.Type<ConfiguredWrapper> {
override val codec: MapCodec<ConfiguredWrapper> by lazy {
CODEC.xmap(::ConfiguredWrapper, ConfiguredWrapper::configured).fieldOf("feature")
}
}
}
val codec: MapCodec<Configured<*, FC>> = codec.fieldOf("config").xmap({ Configured(this, it) }, { it.config })
fun configure(config: FC) = Configured(this, config).placement()
object Wrapper : EnhancedFeature<Holder<ConfiguredFeature<*, *>>>(ConfiguredFeature.CODEC) {
override fun place(
context: EnhancedPlacementContext,
config: Holder<ConfiguredFeature<*, *>>,
positions: Set<PlacementPos>,
allPositions: Set<PlacementPos>
): Boolean {
var any = false
positions.forEach { any = config.value().place(context.level, context.generator, context.random, it.pos) || any }
return any
}
fun configure(config: ConfiguredFeature<*, *>) = configure(Holder.direct(config))
}
companion object {
private val registrar = MDeferredRegister(MRegistries.FEATURE)
val DIRECT_CODEC: Codec<Configured<*, *>> by lazy {
MBuiltInRegistries.FEATURE.byNameCodec().dispatch({ it.feature }, { it.codec })
}
val CODEC: Codec<Holder<Configured<*, *>>> by lazy {
RegistryFileCodec.create(MRegistries.CONFIGURED_FEATURE, DIRECT_CODEC)
}
init {
registrar.register("wrapper") { Wrapper }
}
private fun registerRegistry(event: DataPackRegistryEvent.NewRegistry) {
event.dataPackRegistry(MRegistries.CONFIGURED_FEATURE, DIRECT_CODEC)
}
internal fun register(bus: IEventBus) {
registrar.register(bus)
bus.addListener(::registerRegistry)
}
}
}

View File

@ -0,0 +1,35 @@
package ru.dbotthepony.mc.otm.server.world.placement
import com.google.common.collect.ImmutableList
import com.mojang.serialization.Codec
import com.mojang.serialization.MapCodec
import net.minecraft.core.BlockPos
import net.minecraft.util.RandomSource
import net.minecraft.world.level.levelgen.placement.PlacementContext
import net.minecraft.world.level.levelgen.placement.PlacementModifier
import net.minecraft.world.level.levelgen.placement.PlacementModifierType
import ru.dbotthepony.mc.otm.registry.data.MPlacementModifiers
import java.util.stream.Stream
/**
* Daisy-chaining placements. Required only when using placement modifiers which operate on children list
*/
class ChainPlacement(
val children: List<PlacementModifier>
) : PlacementModifier() {
constructor(vararg children: PlacementModifier) : this(ImmutableList.copyOf(children))
override fun getPositions(context: PlacementContext, random: RandomSource, pos: BlockPos): Stream<BlockPos> {
var stream = Stream.of(pos)
children.forEach { c -> stream = stream.flatMap { c.getPositions(context, random, it) } }
return stream
}
override fun type(): PlacementModifierType<*> {
return MPlacementModifiers.CHAIN
}
companion object {
val CODEC: MapCodec<ChainPlacement> = Codec.list(PlacementModifier.CODEC).xmap(::ChainPlacement, ChainPlacement::children).fieldOf("children")
}
}

View File

@ -0,0 +1,179 @@
package ru.dbotthepony.mc.otm.server.world.placement
import com.mojang.serialization.MapCodec
import com.mojang.serialization.codecs.RecordCodecBuilder
import net.minecraft.core.BlockPos
import net.minecraft.util.RandomSource
import net.minecraft.util.valueproviders.ConstantFloat
import net.minecraft.util.valueproviders.FloatProvider
import net.minecraft.util.valueproviders.IntProvider
import net.minecraft.world.level.levelgen.placement.PlacementContext
import net.minecraft.world.level.levelgen.placement.PlacementModifier
import net.minecraft.world.level.levelgen.placement.PlacementModifierType
import ru.dbotthepony.mc.otm.util.collect.Vec3iHashStrategy
import ru.dbotthepony.mc.otm.data.world.BooleanProvider
import ru.dbotthepony.mc.otm.registry.data.MPlacementModifiers
import ru.dbotthepony.mc.otm.util.math.VECTOR_POSITIVE_X
import ru.dbotthepony.mc.otm.util.math.VECTOR_POSITIVE_Z
import ru.dbotthepony.mc.otm.util.math.VECTOR_UP
import ru.dbotthepony.mc.otm.util.math.Vector
import ru.dbotthepony.mc.otm.util.math.rotate
import ru.dbotthepony.mc.otm.util.math.rotateAroundThis
import ru.dbotthepony.mc.otm.util.nextDouble
import ru.dbotthepony.mc.otm.server.world.EnhancedPlacementContext
import ru.dbotthepony.mc.otm.server.world.PlacementPos
import java.util.*
import java.util.stream.Stream
import kotlin.math.PI
/**
* Ellipsoid ("cloud") placement
*/
data class EllipsoidPlacement(
/**
* X position within ellipsoid sampler, expected to be in range -1 .. 1
*/
val x: FloatProvider,
/**
* Z position within ellipsoid sampler, expected to be in range -1 .. 1
*/
val z: FloatProvider,
/**
* Y position within ellipsoid sampler, expected to be in range -1 .. 1
*/
val y: FloatProvider,
/**
* Total amount of block positions to generate
*/
val count: IntProvider,
/**
* Ellipsoid size sampler on X axis
*/
val xLength: FloatProvider,
/**
* Ellipsoid size sampler on Z axis
*/
val zLength: FloatProvider,
/**
* Ellipsoid size sampler on Y axis
*/
val yLength: FloatProvider,
val rotateXZ: BooleanProvider = BooleanProvider.AlwaysFalse,
val rotateXY: FloatProvider = ConstantFloat.ZERO,
val rotateZY: FloatProvider = ConstantFloat.ZERO,
) : PlacementModifier(), EnhancedPlacement {
init {
require(xLength.minValue >= 1f) { "Bad ellipsoid x minimal size: $xLength" }
require(zLength.minValue >= 1f) { "Bad ellipsoid z minimal size: $zLength" }
require(yLength.minValue >= 1f) { "Bad ellipsoid y minimal size: $yLength" }
}
private fun evaluate(random: RandomSource, position: BlockPos): Set<BlockPos> {
val count = count.sample(random)
val results = TreeSet<BlockPos>(Vec3iHashStrategy)
if (count <= 0)
return results
val xLength = xLength.sample(random)
val zLength = zLength.sample(random)
val yLength = yLength.sample(random)
val xPow = xLength * xLength
val zPow = zLength * zLength
val yPow = yLength * yLength
var iterations = 0
val maxIterations = count * 10
val rotateXZ = rotateXZ.instance().sample(random)
val rotateXY = rotateXY.sample(random)
val rotateZY = rotateZY.sample(random)
val rotationXZ: Double
if (rotateXZ) {
rotationXZ = random.nextDouble(-PI, PI)
} else {
rotationXZ = 0.0
}
val qXZ = VECTOR_UP.rotateAroundThis(rotationXZ)
val qXY = VECTOR_POSITIVE_Z.rotateAroundThis(rotateXY)
val qZY = VECTOR_POSITIVE_X.rotateAroundThis(rotateZY)
while (results.size < count && ++iterations < maxIterations) {
val x = this.x.sample(random) * xLength
val y = this.y.sample(random) * yLength
val z = this.z.sample(random) * zLength
val isValidPoint = (x * x) / xPow +
(y * y) / yPow +
(z * z) / zPow <= 1.0f
if (isValidPoint) {
var point = Vector(x.toDouble(), y.toDouble(), z.toDouble())
point = point.rotate(qXZ)
point = point.rotate(qXY)
point = point.rotate(qZY)
results.add(BlockPos(point.x.toInt() + position.x, point.y.toInt() + position.y, point.z.toInt() + position.z))
}
}
return results
}
override fun getPositions(
context: PlacementContext,
random: RandomSource,
position: BlockPos
): Stream<BlockPos> {
return evaluate(random, position).stream()
}
override fun evaluate(context: EnhancedPlacementContext, positions: List<PlacementPos>): List<PlacementPos> {
val result = ArrayList<PlacementPos>()
val results = positions.map { (it, c) -> evaluate(context.random, it).map { PlacementPos(it, c) } }
result.ensureCapacity(result.size + results.sumOf { it.size } + 1)
results.forEach { result.addAll(it) }
return result
}
override val type: EnhancedPlacement.Type<*>
get() = Companion
override fun type(): PlacementModifierType<*> {
return MPlacementModifiers.ELLIPSOID_PLACEMENT
}
companion object : EnhancedPlacement.Type<EllipsoidPlacement> {
val CODEC: MapCodec<EllipsoidPlacement> by lazy {
RecordCodecBuilder.mapCodec {
it.group(
FloatProvider.CODEC.fieldOf("x").forGetter(EllipsoidPlacement::x),
FloatProvider.CODEC.fieldOf("y").forGetter(EllipsoidPlacement::y),
FloatProvider.CODEC.fieldOf("z").forGetter(EllipsoidPlacement::z),
IntProvider.CODEC.fieldOf("count").forGetter(EllipsoidPlacement::count),
FloatProvider.codec(1f, Float.MAX_VALUE).fieldOf("x_size").forGetter(EllipsoidPlacement::xLength),
FloatProvider.codec(1f, Float.MAX_VALUE).fieldOf("z_size").forGetter(EllipsoidPlacement::zLength),
FloatProvider.codec(1f, Float.MAX_VALUE).fieldOf("y_size").forGetter(EllipsoidPlacement::yLength),
BooleanProvider.CODEC.optionalFieldOf("rotateXZ", BooleanProvider.AlwaysFalse).forGetter(EllipsoidPlacement::rotateXZ),
FloatProvider.CODEC.optionalFieldOf("rotateXY", ConstantFloat.ZERO).forGetter(EllipsoidPlacement::rotateXY),
FloatProvider.CODEC.optionalFieldOf("rotateZY", ConstantFloat.ZERO).forGetter(EllipsoidPlacement::rotateZY),
).apply(it, ::EllipsoidPlacement)
}
}
override val codec: MapCodec<EllipsoidPlacement>
get() = CODEC
}
}

View File

@ -0,0 +1,39 @@
package ru.dbotthepony.mc.otm.server.world.placement
import com.google.common.collect.ImmutableList
import com.mojang.serialization.Codec
import com.mojang.serialization.MapCodec
import ru.dbotthepony.mc.otm.server.world.EnhancedPlacementContext
import ru.dbotthepony.mc.otm.server.world.PlacementPos
/**
* Chains placements, feeding results from one placement into next
*
* Each placement gets its own, new [EnhancedPlacementContext], so variables can be pushed safely into it
*/
class EnhancedChainPlacement(
val children: List<EnhancedPlacement>
) : EnhancedPlacement {
constructor(vararg children: EnhancedPlacement) : this(ImmutableList.copyOf(children))
override fun evaluate(context: EnhancedPlacementContext, positions: List<PlacementPos>): List<PlacementPos> {
var currentContext = context
var current = positions
children.forEach {
current = it.evaluate(context, current)
currentContext = currentContext.push()
}
return current
}
override val type: EnhancedPlacement.Type<*>
get() = Companion
companion object : EnhancedPlacement.Type<EnhancedChainPlacement> {
override val codec: MapCodec<EnhancedChainPlacement> by lazy {
Codec.list(EnhancedPlacement.CODEC).xmap(::EnhancedChainPlacement, EnhancedChainPlacement::children).fieldOf("children")
}
}
}

View File

@ -0,0 +1,30 @@
package ru.dbotthepony.mc.otm.server.world.placement
import com.mojang.serialization.MapCodec
import net.minecraft.util.valueproviders.IntProvider
import ru.dbotthepony.mc.otm.server.world.EnhancedPlacementContext
import ru.dbotthepony.mc.otm.server.world.PlacementPos
class EnhancedCountPlacement(val provider: IntProvider) : EnhancedPlacement {
override fun evaluate(context: EnhancedPlacementContext, positions: List<PlacementPos>): List<PlacementPos> {
val count = provider.sample(context.random)
if (count <= 0) {
return listOf()
} else if (count == 1) {
return positions
} else {
val result = ArrayList<PlacementPos>()
result.ensureCapacity(positions.size * count)
for (i in 0 until count) result.addAll(positions)
return result
}
}
override val type: EnhancedPlacement.Type<*>
get() = Companion
companion object : EnhancedPlacement.Type<EnhancedCountPlacement> {
override val codec: MapCodec<EnhancedCountPlacement> = IntProvider.CODEC.xmap(::EnhancedCountPlacement, EnhancedCountPlacement::provider).fieldOf("count")
}
}

View File

@ -0,0 +1,64 @@
package ru.dbotthepony.mc.otm.server.world.placement
import com.mojang.serialization.Codec
import com.mojang.serialization.MapCodec
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.server.world.EnhancedPlacementContext
import ru.dbotthepony.mc.otm.server.world.PlacementPos
import java.util.stream.Collectors
interface EnhancedPlacement {
interface Type<T : EnhancedPlacement> {
val codec: MapCodec<T>
}
fun evaluate(context: EnhancedPlacementContext, positions: List<PlacementPos>): List<PlacementPos>
val type: Type<*>
class Wrapper(val parent: PlacementModifier) : EnhancedPlacement {
override fun evaluate(context: EnhancedPlacementContext, positions: List<PlacementPos>): List<PlacementPos> {
return positions.stream()
.flatMap { (pos, vars) -> parent.getPositions(context.vanillaContext, context.random, pos).map { PlacementPos(it, vars) } }
.collect(Collectors.toCollection(::ArrayList))
}
override val type: Type<*>
get() = Companion
companion object : Type<Wrapper> {
override val codec: MapCodec<Wrapper> = PlacementModifier.CODEC.xmap(::Wrapper, Wrapper::parent).fieldOf("parent")
}
}
object Passthrough : EnhancedPlacement, Type<Passthrough> {
override fun evaluate(context: EnhancedPlacementContext, positions: List<PlacementPos>): List<PlacementPos> {
return positions
}
override val type: Type<*>
get() = this
override val codec: MapCodec<Passthrough> = MapCodec.unit(this)
}
companion object {
private val registrar = MDeferredRegister(MRegistries.PLACEMENT_MODIFIER)
init {
registrar.register("wrapper") { Wrapper.Companion }
registrar.register("passthrough") { Passthrough }
}
val CODEC: Codec<EnhancedPlacement> by lazy {
MBuiltInRegistries.PLACEMENT_MODIFIER.byNameCodec().dispatch({ it.type }, { it.codec })
}
internal fun register(bus: IEventBus) {
registrar.register(bus)
}
}
}

View File

@ -0,0 +1,62 @@
package ru.dbotthepony.mc.otm.server.world.placement
import com.google.common.collect.ImmutableList
import com.mojang.serialization.Codec
import com.mojang.serialization.MapCodec
import com.mojang.serialization.codecs.RecordCodecBuilder
import net.minecraft.util.StringRepresentable
import net.minecraft.world.level.levelgen.placement.PlacementModifier
import ru.dbotthepony.mc.otm.server.world.EnhancedPlacementContext
import ru.dbotthepony.mc.otm.server.world.PlacementPos
/**
* Or "shard" placement, if you will.
*
* Basically, allows multiple [PlacementModifier]s to split/branch off from provided point.
*/
class EnhancedSplitPlacement(
val mode: Mode,
val children: List<EnhancedPlacement>,
) : EnhancedPlacement {
constructor(mode: Mode, vararg children: EnhancedPlacement) : this(mode, ImmutableList.copyOf(children))
enum class Mode : StringRepresentable {
COMBINE,
FORK;
private val lname = name.lowercase()
override fun getSerializedName(): String {
return lname
}
companion object {
val CODEC: Codec<Mode> = StringRepresentable.fromEnum(::values)
}
}
override fun evaluate(context: EnhancedPlacementContext, positions: List<PlacementPos>): List<PlacementPos> {
if (mode == Mode.COMBINE) {
val result = ArrayList<PlacementPos>()
children.forEach { result.addAll(it.evaluate(context.push(), positions)) }
return result
} else {
children.forEach { it.evaluate(context.push(), positions) }
return positions
}
}
override val type: EnhancedPlacement.Type<*>
get() = Companion
companion object : EnhancedPlacement.Type<EnhancedSplitPlacement> {
override val codec: MapCodec<EnhancedSplitPlacement> by lazy {
RecordCodecBuilder.mapCodec {
it.group(
Mode.CODEC.fieldOf("mode").forGetter(EnhancedSplitPlacement::mode),
Codec.list(EnhancedPlacement.CODEC).fieldOf("children").forGetter(EnhancedSplitPlacement::children),
).apply(it, ::EnhancedSplitPlacement)
}
}
}
}

View File

@ -0,0 +1,35 @@
package ru.dbotthepony.mc.otm.server.world.placement
import com.google.common.collect.ImmutableList
import com.mojang.serialization.Codec
import com.mojang.serialization.MapCodec
import net.minecraft.core.BlockPos
import net.minecraft.util.RandomSource
import net.minecraft.world.level.levelgen.placement.PlacementContext
import net.minecraft.world.level.levelgen.placement.PlacementModifier
import net.minecraft.world.level.levelgen.placement.PlacementModifierType
import ru.dbotthepony.mc.otm.registry.data.MPlacementModifiers
import java.util.stream.Stream
/**
* Or "shard" placement, if you will.
*
* Basically, allows multiple [PlacementModifier]s to split/branch off from provided point.
*/
class SplitPlacement(
val children: List<PlacementModifier>
) : PlacementModifier() {
constructor(vararg children: PlacementModifier) : this(ImmutableList.copyOf(children))
override fun getPositions(context: PlacementContext, random: RandomSource, pos: BlockPos): Stream<BlockPos> {
return children.stream().flatMap { it.getPositions(context, random, pos) }
}
override fun type(): PlacementModifierType<*> {
return MPlacementModifiers.SPLIT
}
companion object {
val CODEC: MapCodec<SplitPlacement> = Codec.list(PlacementModifier.CODEC).xmap(::SplitPlacement, SplitPlacement::children).fieldOf("children")
}
}

View File

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

View File

@ -0,0 +1,246 @@
package ru.dbotthepony.mc.otm.server.world.placement
import com.mojang.serialization.Codec
import com.mojang.serialization.MapCodec
import com.mojang.serialization.codecs.RecordCodecBuilder
import net.minecraft.core.BlockPos
import net.minecraft.util.RandomSource
import net.minecraft.util.valueproviders.ClampedNormalFloat
import net.minecraft.util.valueproviders.ConstantFloat
import net.minecraft.util.valueproviders.FloatProvider
import net.minecraft.util.valueproviders.IntProvider
import net.minecraft.util.valueproviders.UniformFloat
import net.minecraft.world.level.levelgen.placement.PlacementContext
import net.minecraft.world.level.levelgen.placement.PlacementModifier
import net.minecraft.world.level.levelgen.placement.PlacementModifierType
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 ru.dbotthepony.mc.otm.util.math.Vector
import ru.dbotthepony.mc.otm.util.math.angleDifference
import ru.dbotthepony.mc.otm.util.math.normalizeAngle
import ru.dbotthepony.mc.otm.util.math.plus
import ru.dbotthepony.mc.otm.util.math.toBlockPos
import ru.dbotthepony.mc.otm.util.nextDouble
import ru.dbotthepony.mc.otm.server.world.EnhancedPlacementContext
import ru.dbotthepony.mc.otm.server.world.PlacementPos
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
/**
* Creates one block thick "worm" positions which twists and turns
*/
class WormPlacement(
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,
) : PlacementModifier(), EnhancedPlacement {
private inner class Worm(private val random: RandomSource, private var position: Vector) {
private var remainingDistance = length.sample(random)
private var xzRotation = random.nextDouble(-PI, PI)
private var xyRotation = normalizeAngle(initialAngleXY.sample(random).toDouble())
private var xzTargetRotation = random.nextDouble(-PI, PI)
private var xyTargetRotation = normalizeAngle(initialAngleXY.sample(random).toDouble())
private var xzSin = sin(xzRotation)
private var xzCos = cos(xzRotation)
private var xySin = sin(xyRotation)
private val turnChanceXZ = this@WormPlacement.turnChanceXZ.instance()
private val turnChanceXY = this@WormPlacement.turnChanceXY.instance()
val hasFinished: Boolean
get() = remainingDistance <= 0
private var prevPos = BlockPos.ZERO
fun follow(): BlockPos? {
if (hasFinished) return null
remainingDistance--
// wormy turn
if (turnChanceXZ.sample(random)) {
// wormy angle
xzTargetRotation += turnRateXZ.sample(random)
xzTargetRotation = normalizeAngle(xzTargetRotation)
}
// wormy-o!
if (turnChanceXY.sample(random)) {
// worm?
xyTargetRotation += turnRateXY.sample(random)
xyTargetRotation = normalizeAngle(xyTargetRotation)
}
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) {
// if worm made a wurm, add new position to set
prevPos = calc
return calc
}
return null
}
}
private fun evaluate(random: RandomSource, position: BlockPos): List<BlockPos> {
val worms = ArrayList<Worm>()
worms.add(Worm(random, Vector.ZERO))
val results = ArrayList<BlockPos>()
results.add(position)
while (worms.isNotEmpty()) {
worms.removeIf {
val pos = it.follow()
if (pos != null) results.add(pos + position)
it.hasFinished
}
}
return results
}
override fun evaluate(context: EnhancedPlacementContext, positions: List<PlacementPos>): List<PlacementPos> {
if (positions.isEmpty())
return positions
else {
val result = ArrayList<PlacementPos>()
val results = positions.map { (it, c) -> evaluate(context.random, it).map { PlacementPos(it, c) } }
result.ensureCapacity(result.size + results.sumOf { it.size } + 1)
results.forEach { result.addAll(it) }
return result
}
}
override val type: EnhancedPlacement.Type<*>
get() = Companion
override fun getPositions(context: PlacementContext, random: RandomSource, center: BlockPos): Stream<BlockPos> {
return evaluate(random, center).stream()
}
override fun type(): PlacementModifierType<*> {
return MPlacementModifiers.WORM_PLACEMENT
}
companion object : EnhancedPlacement.Type<WormPlacement> {
private fun increment(value: Float): Float {
var i = 1f
while (true) {
val enlarge = Float.MIN_VALUE * i
val sum = value + enlarge
if (sum != value) {
return sum
} else {
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)
}
fun uniformTurnRateRadians(rad: Float): FloatProvider {
require(rad >= 0f) { "Provided value must be non-negative, $rad given" }
if (rad == 0f) return ConstantFloat.ZERO
return UniformFloat.of(-rad, increment(rad))
}
fun normalDistributedTurnRate(deviation: Float, mean: Float = 0f): FloatProvider {
return normalDistributedTurnRateRadians(deviation * DEGREES_TO_RADIANS, mean * DEGREES_TO_RADIANS)
}
fun normalDistributedTurnRateRadians(deviation: Float, mean: Float = 0f): FloatProvider {
require(deviation >= 0f) { "Provided value must be non-negative, $deviation given" }
if (deviation == 0f) return ConstantFloat.ZERO
return ClampedNormalFloat.of(mean, deviation, -PI.toFloat() * 2f, PI.toFloat() * 2f)
}
private const val DEGREES_TO_RADIANS = PI.toFloat() / 180f
val CODEC: MapCodec<WormPlacement> by lazy {
RecordCodecBuilder.mapCodec {
it.group(
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)
}
}
override val codec: MapCodec<WormPlacement>
get() = CODEC
}
}

View File

@ -0,0 +1,31 @@
package ru.dbotthepony.mc.otm.util.collect
import it.unimi.dsi.fastutil.Hash
import it.unimi.dsi.fastutil.HashCommon
import net.minecraft.core.Vec3i
object Vec3iHashStrategy : Hash.Strategy<Vec3i>, Comparator<Vec3i> {
override fun equals(a: Vec3i?, b: Vec3i?): Boolean {
return a == b
}
// while this avoids collisions, it is rather slow when compared to just using a tree set here
override fun hashCode(o: Vec3i?): Int {
o ?: return 0
return HashCommon.murmurHash3(o.x.toLong().and(1 shl 26 - 1) or o.z.toLong().and(1 shl 26 - 1).shl(26) or o.y.toLong().and(1 shl 12 - 1).shl(52)).toInt()
}
override fun compare(o1: Vec3i?, o2: Vec3i?): Int {
if (o1 == null && o2 == null)
return 0
else if (o1 == null)
return -1 // nulls come first
else if (o2 == null)
return 1
var cmp = o1.x.compareTo(o2.x)
if (cmp == 0) cmp = o1.z.compareTo(o2.z)
if (cmp == 0) cmp = o1.y.compareTo(o2.y)
return cmp
}
}

View File

@ -8,9 +8,11 @@ import net.minecraft.core.Direction
import net.minecraft.core.Vec3i
import net.minecraft.world.level.ChunkPos
import net.minecraft.world.phys.Vec3
import org.joml.AxisAngle4d
import org.joml.AxisAngle4f
import org.joml.Matrix3f
import org.joml.Matrix4f
import org.joml.Quaterniond
import org.joml.Quaternionf
import org.joml.Vector3f
import ru.dbotthepony.kommons.collect.filter
@ -25,10 +27,14 @@ typealias Vector = Vec3
val VECTOR_UP = Vector(0.0, 1.0, 0.0)
val VECTOR_FORWARD = Vector(1.0, 0.0, 0.0)
val VECTOR_POSITIVE_X = VECTOR_FORWARD
val VECTOR_BACKWARD = Vector(-1.0, 0.0, 0.0)
val VECTOR_NEGATIVE_X = VECTOR_BACKWARD
val VECTOR_DOWN = Vector(0.0, -1.0, 0.0)
val VECTOR_RIGHT = Vector(0.0, 0.0, 1.0)
val VECTOR_POSITIVE_Z = VECTOR_RIGHT
val VECTOR_LEFT = Vector(0.0, 0.0, -1.0)
val VECTOR_NEGATIVE_Z = VECTOR_LEFT
private const val DEGREES_TO_RADIANS = 0.017453292f
@ -36,6 +42,10 @@ fun toRadians(angle: Float): Float {
return angle * DEGREES_TO_RADIANS
}
fun toRadians(angle: Double): Double {
return Math.toRadians(angle)
}
fun Vector.asAngle(): Angle {
val norm = normalize()
return Angle(asin(norm.y), atan2(norm.x, norm.z))
@ -173,16 +183,36 @@ fun Vector.rotate(q: Quaternionf): Vector {
return Vector(quaternion.x.toDouble(), quaternion.y.toDouble(), quaternion.z.toDouble())
}
fun Vector.rotate(q: Quaterniond): Vector {
// TODO: 1.19.3
val quaternion = Quaterniond(q)
quaternion.mul(rotateAroundThisDouble())
val quaternion1 = Quaterniond(q)
quaternion1.conjugate()
quaternion.mul(quaternion1)
return Vector(quaternion.x, quaternion.y, quaternion.z)
}
fun Vector.rotateAroundThis(rotation: Float, isDegrees: Boolean = false): Quaternionf {
// TODO: 1.19.3
return Quaternionf(AxisAngle4f(if (isDegrees) toRadians(rotation) else rotation, x.toFloat(), y.toFloat(), z.toFloat()))
}
fun Vector.rotateAroundThis(rotation: Double, isDegrees: Boolean = false): Quaterniond {
// TODO: 1.19.3
return Quaterniond(AxisAngle4d(if (isDegrees) toRadians(rotation) else rotation, x, y, z))
}
fun Vector.rotateAroundThis(): Quaternionf {
// TODO: 1.19.3
return Quaternionf(AxisAngle4f(0f, x.toFloat(), y.toFloat(), z.toFloat()))
}
fun Vector.rotateAroundThisDouble(): Quaterniond {
// TODO: 1.19.3
return Quaterniond(AxisAngle4d(0.0, x, y, z))
}
fun Vector.asMatrix(): Matrix3f {
return Matrix3f().also {
it[0, 0] = x.toFloat()
@ -444,7 +474,9 @@ operator fun Vec3i.plus(direction: Vector): Vector = Vector(x + direction.x, y +
operator fun Vec3i.minus(direction: Vector): Vector = Vector(x - direction.x, y - direction.y, z - direction.z)
fun Vec3.toIntVector() = Vec3i(x.toInt(), y.toInt(), z.toInt())
fun Vec3.toBlockPos() = BlockPos(x.toInt(), y.toInt(), z.toInt())
fun Vec3.roundToIntVector() = Vec3i(x.roundToInt(), y.roundToInt(), z.roundToInt())
fun Vec3.roundToBlockPos() = BlockPos(x.roundToInt(), y.roundToInt(), z.roundToInt())
fun BlockPos.asVector(): Vector {
return Vector(x + 0.5, y + 0.5, z + 0.5)