diff --git a/docs/scarpet/api/BlocksAndWorldAccess.md b/docs/scarpet/api/BlocksAndWorldAccess.md index 2dccb39c..235f56ab 100644 --- a/docs/scarpet/api/BlocksAndWorldAccess.md +++ b/docs/scarpet/api/BlocksAndWorldAccess.md @@ -346,12 +346,15 @@ poi(x,y,z,5) => [] // nothing around poi(x,y,z,5) => [['nether_portal',0,[7,8,9]],['nether_portal',0,[7,9,9]]] // two portal blocks in the range -### `biome()` `biome(name)` `biome(block)` `biome(block/name, feature)` +### `biome()` `biome(name)` `biome(block)` `biome(block/name, feature)`, `biome(noise_map)` Without arguments, returns the list of biomes in the world. With block, or name, returns the name of the biome in that position, or throws `'unknown_biome'` if provided biome or block are not valid. +if passed a map of `continentalness`, `depth`, `erosion`, `humidity`, `temperature`, `weirdness`, returns the biome that exists at those noise values. +Note: Have to pass all 6 of the mentioned noise types and only these noise types for it to evaluate a biome. + With an optional feature, it returns value for the specified attribute for that biome. Available and queryable features include: * `'top_material'`: unlocalized block representing the top surface material * `'under_material'`: unlocalized block representing what sits below topsoil @@ -502,6 +505,29 @@ Returns the map colour of a block at position. One of: `'brown_terracotta'`, `'green_terracotta'`, `'red_terracotta'`, `'black_terracotta'`, `'crimson_nylium'`, `'crimson_stem'`, `'crimson_hyphae'`, `'warped_nylium'`, `'warped_stem'`, `'warped_hyphae'`, `'warped_wart'` +### `query_noise(pos, ...type?)` + +Queries the multi noise value(s) on the given position. +If no type is passed, returns a map of `continentalness`, `depth`, `erosion`, `humidity`, `temperature`, `weirdness`. +Otherwise, returns the map of that specific noise. + +
+// without type
+query_noise(pos) => {continentalness: 0.445300012827, erosion: 0.395399987698, temperature: 0.165399998426, ...}
+// passing type as multiple arguments
+query_noise(pos, 'pillarRareness', 'aquiferBarrier') => {aquiferBarrier: -0.205013844481, pillarRareness: 1.04772473438}
+// passing types as a list with unpacking operator
+query_noise(pos, ...['spaghetti3dFirst', 'spaghetti3dSecond']) => {spaghetti3dFirst: -0.186052125186, spaghetti3dSecond: 0.211626790923}
+
+ +Available types: + +`aquiferBarrier`, `aquiferFluidLevelFloodedness`, `aquiferFluidLevelSpread`, `aquiferLava`, `caveCheese`, +`caveEntrance`, `caveLayer`, `continentalness`, `depth`, `erosion`, `humidity`, `island`, `jagged`, `oreGap`, +`pillar`, `pillarRareness`, `pillarThickness`, `shiftX`, `shiftY`, `shiftZ`, `spaghetti2d`, `spaghetti2dElevation`, +`spaghetti2dModulator`, `spaghetti2dThickness`, `spaghetti3d`, `spaghetti3dFirst`, `spaghetti3dRarity`, +`spaghetti3dSecond`, `spaghetti3dThickness`, `spaghettiRoughness`, `spaghettiRoughnessModulator`, `temperature`, +`terrain`, `terrainFactor`, `terrainOffset`, `terrainPeaks`, `weirdness` ### `loaded(pos)` diff --git a/src/main/java/carpet/fakes/NoiseColumnSamplerInterface.java b/src/main/java/carpet/fakes/NoiseColumnSamplerInterface.java new file mode 100644 index 00000000..42efcc66 --- /dev/null +++ b/src/main/java/carpet/fakes/NoiseColumnSamplerInterface.java @@ -0,0 +1,5 @@ +package carpet.fakes; + +public interface NoiseColumnSamplerInterface { + double getNoiseSample(String name, int x, int y, int z); +} diff --git a/src/main/java/carpet/mixins/CaveScalerMixin_scarpetMixin.java b/src/main/java/carpet/mixins/CaveScalerMixin_scarpetMixin.java new file mode 100644 index 00000000..c363eb7d --- /dev/null +++ b/src/main/java/carpet/mixins/CaveScalerMixin_scarpetMixin.java @@ -0,0 +1,14 @@ +package carpet.mixins; + +import net.minecraft.world.gen.NoiseColumnSampler; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Invoker; + + +@Mixin(NoiseColumnSampler.CaveScaler.class) +public interface CaveScalerMixin_scarpetMixin { + @Invoker + static double invokeScaleTunnels(double d) { + throw new AssertionError(); + } +} diff --git a/src/main/java/carpet/mixins/NoiseColumnSamplerMixin_scarpetMixin.java b/src/main/java/carpet/mixins/NoiseColumnSamplerMixin_scarpetMixin.java new file mode 100644 index 00000000..3ce6bfb5 --- /dev/null +++ b/src/main/java/carpet/mixins/NoiseColumnSamplerMixin_scarpetMixin.java @@ -0,0 +1,255 @@ +package carpet.mixins; + +import carpet.fakes.NoiseColumnSamplerInterface; +import carpet.script.exception.InternalExpressionException; +import net.minecraft.util.math.noise.DoublePerlinNoiseSampler; +import net.minecraft.util.math.noise.InterpolatedNoiseSampler; +import net.minecraft.util.math.noise.SimplexNoiseSampler; +import net.minecraft.world.biome.source.BiomeCoords; +import net.minecraft.world.biome.source.util.MultiNoiseUtil; +import net.minecraft.world.biome.source.util.TerrainNoisePoint; +import net.minecraft.world.gen.NoiseColumnSampler; +import net.minecraft.world.gen.NoiseHelper; +import net.minecraft.world.gen.chunk.Blender; +import net.minecraft.world.gen.chunk.GenerationShapeConfig; +import org.jetbrains.annotations.Nullable; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; + +@Mixin(NoiseColumnSampler.class) +public abstract class NoiseColumnSamplerMixin_scarpetMixin implements NoiseColumnSamplerInterface { + + @Shadow @Final + private InterpolatedNoiseSampler terrainNoise; + + @Shadow @Final @Nullable + private SimplexNoiseSampler islandNoise; + + @Shadow @Final + private DoublePerlinNoiseSampler aquiferBarrierNoise; + + @Shadow @Final + private DoublePerlinNoiseSampler aquiferFluidLevelFloodednessNoise; + + @Shadow @Final + private DoublePerlinNoiseSampler aquiferFluidLevelSpreadNoise; + + @Shadow @Final + private DoublePerlinNoiseSampler aquiferLavaNoise; + + @Shadow @Final + private DoublePerlinNoiseSampler pillarRarenessNoise; + + @Shadow @Final + private DoublePerlinNoiseSampler pillarThicknessNoise; + + @Shadow @Final + private DoublePerlinNoiseSampler spaghetti2dElevationNoise; + + @Shadow @Final + private DoublePerlinNoiseSampler spaghetti2dModulatorNoise; + + @Shadow @Final + private DoublePerlinNoiseSampler spaghetti2dThicknessNoise; + + @Shadow @Final + private DoublePerlinNoiseSampler spaghetti3dFirstNoise; + + @Shadow @Final + private DoublePerlinNoiseSampler spaghetti3dSecondNoise; + + @Shadow @Final + private DoublePerlinNoiseSampler spaghetti3dRarityNoise; + + @Shadow @Final + private DoublePerlinNoiseSampler spaghetti3dThicknessNoise; + + @Shadow @Final + private DoublePerlinNoiseSampler spaghettiRoughnessModulatorNoise; + + @Shadow @Final + private DoublePerlinNoiseSampler caveCheeseNoise; + + @Shadow @Final + private DoublePerlinNoiseSampler oreGapNoise; + + @Shadow @Final + private GenerationShapeConfig config; + + @Shadow + protected abstract double method_38409(double d, double e, double f); + + @Shadow + protected abstract double sampleCaveLayerNoise(int x, int y, int z); + + @Shadow + protected abstract double samplePillarNoise(int x, int y, int z); + + @Shadow + protected abstract double sampleSpaghetti2dNoise(int x, int y, int z); + + @Shadow + protected abstract double sampleSpaghetti3dNoise(int x, int y, int z); + + @Shadow + protected abstract double sampleSpaghettiRoughnessNoise(int x, int y, int z); + + @Shadow + protected abstract double sampleCaveEntranceNoise(int x, int y, int z); + + @Shadow + public abstract double sampleShiftNoise(int x, int y, int z); + + @Shadow + private static double sample(DoublePerlinNoiseSampler sampler, double x, double y, double z, double invertedScale) { + throw new AssertionError(); + } + + @Shadow + public abstract MultiNoiseUtil.NoiseValuePoint sample(int i, int j, int k); + + @Shadow + public abstract TerrainNoisePoint createTerrainNoisePoint(int x, int z, float continentalness, float weirdness, float erosion, Blender blender); + + @Override + public double getNoiseSample(String name, int x, int y, int z) { + MultiNoiseUtil.NoiseValuePoint noiseValuePoint = this.sample(x, y, z); + TerrainNoisePoint terrainNoisePoint = createTerrainNoisePoint( + x, + z, + MultiNoiseUtil.method_38666(noiseValuePoint.continentalnessNoise()), + MultiNoiseUtil.method_38666(noiseValuePoint.weirdnessNoise()), + MultiNoiseUtil.method_38666(noiseValuePoint.erosionNoise()), + Blender.getNoBlending() + ); + switch (name) { + case "temperature" -> { + return MultiNoiseUtil.method_38666(noiseValuePoint.temperatureNoise()); + } + case "humidity" -> { + return MultiNoiseUtil.method_38666(noiseValuePoint.humidityNoise()); + } + case "continentalness" -> { + return MultiNoiseUtil.method_38666(noiseValuePoint.continentalnessNoise()); + } + case "erosion" -> { + return MultiNoiseUtil.method_38666(noiseValuePoint.erosionNoise()); + } + case "weirdness" -> { + return MultiNoiseUtil.method_38666(noiseValuePoint.weirdnessNoise()); + } + case "depth" -> { + return MultiNoiseUtil.method_38666(noiseValuePoint.depth()); + } + case "shiftX" -> { + return x + this.sampleShiftNoise(x, 0, z); + } + case "shiftY" -> { + return y + this.sampleShiftNoise(y, z, x); + } + case "shiftZ" -> { + return z + this.sampleShiftNoise(z, x, 0); + } + case "terrain" -> { + return this.terrainNoise.calculateNoise(x, y, z); + } + case "island" -> { + return this.islandNoise == null ? 0 : this.islandNoise.sample(x, y, z); + } + case "jagged" -> { + return this.method_38409(terrainNoisePoint.peaks(), x, z); + } + case "aquiferBarrier" -> { + return this.aquiferBarrierNoise.sample(x, y, z); + } + case "aquiferFluidLevelFloodedness" -> { + return this.aquiferFluidLevelFloodednessNoise.sample(x, y, z); + } + case "aquiferFluidLevelSpread" -> { + return this.aquiferFluidLevelSpreadNoise.sample(x, y, z); + } + case "aquiferLava" -> { + return this.aquiferLavaNoise.sample(x, y, z); + } + case "pillar" -> { + return this.samplePillarNoise(x, y, z); + } + case "pillarRareness" -> { + return NoiseHelper.lerpFromProgress(this.pillarRarenessNoise, x, y, z, 0.0D, 2.0D); + } + case "pillarThickness" -> { + return NoiseHelper.lerpFromProgress(this.pillarThicknessNoise, x, y, z, 0.0D, 1.1D); + } + case "spaghetti2d" -> { + return this.sampleSpaghetti2dNoise(x, y, z); + } + case "spaghetti2dElevation" -> { + return NoiseHelper.lerpFromProgress(this.spaghetti2dElevationNoise, x, 0.0, z, this.config.minimumBlockY(), 8.0); + } + case "spaghetti2dModulator" -> { + return this.spaghetti2dModulatorNoise.sample(x * 2, y, z * 2); + } + case "spaghetti2dThickness" -> { + return NoiseHelper.lerpFromProgress(this.spaghetti2dThicknessNoise, x * 2, y, z * 2, 0.6, 1.3); + } + case "spaghetti3d" -> { + return this.sampleSpaghetti3dNoise(x, y, z); + } + case "spaghetti3dFirst" -> { + double d = this.spaghetti3dRarityNoise.sample(x * 2, y, z * 2); + double e = CaveScalerMixin_scarpetMixin.invokeScaleTunnels(d); + return sample(this.spaghetti3dFirstNoise, x, y, z, e); + } + case "spaghetti3dSecond" -> { + double d = this.spaghetti3dRarityNoise.sample(x * 2, y, z * 2); + double e = CaveScalerMixin_scarpetMixin.invokeScaleTunnels(d); + return sample(this.spaghetti3dSecondNoise, x, y, z, e); + } + case "spaghetti3dRarity" -> { + return this.spaghetti3dRarityNoise.sample(x * 2, y, z * 2); + } + case "spaghetti3dThickness" -> { + return NoiseHelper.lerpFromProgress(this.spaghetti3dThicknessNoise, x, y, z, 0.065, 0.088); + } + case "spaghettiRoughness" -> { + return this.sampleSpaghettiRoughnessNoise(x, y, z); + } + case "spaghettiRoughnessModulator" -> { + return NoiseHelper.lerpFromProgress(this.spaghettiRoughnessModulatorNoise, x, y, z, 0.0, 0.1); + } + case "caveEntrance" -> { + return this.sampleCaveEntranceNoise(x, y, z); + } + case "caveLayer" -> { + // scaling them by 4 because it's sampled in normal coords + return this.sampleCaveLayerNoise( + BiomeCoords.toBlock(x), + BiomeCoords.toBlock(y), + BiomeCoords.toBlock(z) + ); + } + case "caveCheese" -> { + // same reason as above + return this.caveCheeseNoise.sample( + BiomeCoords.toBlock(x), + BiomeCoords.toBlock(y) / 1.5, + BiomeCoords.toBlock(z) + ); + } + case "terrainPeaks" -> { + return terrainNoisePoint.peaks(); + } + case "terrainOffset" -> { + return terrainNoisePoint.offset(); + } + case "terrainFactor" -> { + return terrainNoisePoint.factor(); + } + case "oreGap" -> { + return this.oreGapNoise.sample(x, y, z); + } + } + throw new InternalExpressionException("Unknown noise type: " + name); + } +} diff --git a/src/main/java/carpet/script/annotation/AnnotationParser.java b/src/main/java/carpet/script/annotation/AnnotationParser.java index a2c759c6..4aa47a52 100644 --- a/src/main/java/carpet/script/annotation/AnnotationParser.java +++ b/src/main/java/carpet/script/annotation/AnnotationParser.java @@ -189,11 +189,11 @@ public final class AnnotationParser if (maxParams == -2) throw new IllegalArgumentException("No maximum number of params specified for " + name + ", use -1 for unlimited. " + "Provided in " + instance.getClass()); + if (maxParams == -1) + maxParams = Integer.MAX_VALUE; if (maxParams < this.minParams) throw new IllegalArgumentException("Provided maximum number of params for " + name + " is smaller than method's param count." + "Provided in " + instance.getClass()); - if (maxParams == -1) - maxParams = Integer.MAX_VALUE; } this.maxParams = maxParams; diff --git a/src/main/java/carpet/script/api/WorldAccess.java b/src/main/java/carpet/script/api/WorldAccess.java index 9b870452..9e0cd66e 100644 --- a/src/main/java/carpet/script/api/WorldAccess.java +++ b/src/main/java/carpet/script/api/WorldAccess.java @@ -5,6 +5,7 @@ import carpet.CarpetSettings; import carpet.fakes.BiomeArrayInterface; import carpet.fakes.ChunkGeneratorInterface; import carpet.fakes.ChunkTicketManagerInterface; +import carpet.fakes.NoiseColumnSamplerInterface; import carpet.fakes.ServerChunkManagerInterface; import carpet.fakes.ServerWorldInterface; import carpet.fakes.SpawnHelperInnerInterface; @@ -16,6 +17,8 @@ import carpet.script.Context; import carpet.script.Expression; import carpet.script.Fluff; import carpet.script.LazyValue; +import carpet.script.annotation.Locator; +import carpet.script.annotation.ScarpetFunction; import carpet.script.argument.BlockArgument; import carpet.script.argument.Vector3Argument; import carpet.script.exception.InternalExpressionException; @@ -93,6 +96,9 @@ import net.minecraft.world.SpawnHelper; import net.minecraft.world.World; import net.minecraft.world.biome.Biome; import net.minecraft.world.biome.source.BiomeCoords; +import net.minecraft.world.biome.source.BiomeSource; +import net.minecraft.world.biome.source.MultiNoiseBiomeSource; +import net.minecraft.world.biome.source.util.MultiNoiseUtil; import net.minecraft.world.chunk.Chunk; import net.minecraft.world.chunk.ChunkStatus; import net.minecraft.world.chunk.WorldChunk; @@ -221,6 +227,20 @@ public class WorldAccess { return bs; } + private static void nullCheck(Value v, String name) { + if (v.isNull()) { + throw new IllegalArgumentException(name + " cannot be null"); + } + } + + private static float numberGetOrThrow(Value v) { + double num = v.readDoubleNumber(); + if (Double.isNaN(num)) { + throw new IllegalArgumentException(v.getString() + " needs to be a numeric value"); + } + return (float) num; + } + private static void BooYah(ChunkGenerator generator) { synchronized (generator) @@ -343,7 +363,7 @@ public class WorldAccess { } String poiTypeString = poi.getString().toLowerCase(Locale.ROOT); PointOfInterestType type = Registry.POINT_OF_INTEREST_TYPE.getOrEmpty(InputValidator.identifierOf(poiTypeString)) - .orElseThrow(() -> new ThrowStatement(poiTypeString, Throwables.UNKNOWN_POI)); + .orElseThrow(() -> new ThrowStatement(poiTypeString, Throwables.UNKNOWN_POI)); int occupancy = 0; if (locator.offset + 1 < lv.size()) { @@ -1129,19 +1149,55 @@ public class WorldAccess { return BooleanValue.of(blockLocator.block.getBlockState().isIn(blockTag)); }); - expression.addContextFunction("biome", -1, (c, t, lv) -> { - CarpetContext cc = (CarpetContext)c; + CarpetContext cc = (CarpetContext) c; ServerWorld world = cc.s.getWorld(); if (lv.size() == 0) return ListValue.wrap(world.getRegistryManager().get(Registry.BIOME_KEY).getIds().stream().map(ValueConversions::of)); - BlockArgument locator = BlockArgument.findIn(cc, lv, 0, false, false, true); Biome biome; + BiomeSource biomeSource = world.getChunkManager().getChunkGenerator().getBiomeSource(); + if ( lv.size() == 1 + && lv.get(0) instanceof MapValue map + && biomeSource instanceof MultiNoiseBiomeSource mnbs + ) { + Value temperature = map.get(new StringValue("temperature")); + nullCheck(temperature, "temperature"); + + Value humidity = map.get(new StringValue("humidity")); + nullCheck(humidity, "humidity"); + + Value continentalness = map.get(new StringValue("continentalness")); + nullCheck(continentalness, "continentalness"); + + Value erosion = map.get(new StringValue("erosion")); + nullCheck(erosion, "erosion"); + + Value depth = map.get(new StringValue("depth")); + nullCheck(depth, "depth"); + + Value weirdness = map.get(new StringValue("weirdness")); + nullCheck(weirdness, "weirdness"); + + MultiNoiseUtil.NoiseValuePoint point = new MultiNoiseUtil.NoiseValuePoint( + MultiNoiseUtil.method_38665(numberGetOrThrow(temperature)), + MultiNoiseUtil.method_38665(numberGetOrThrow(humidity)), + MultiNoiseUtil.method_38665(numberGetOrThrow(continentalness)), + MultiNoiseUtil.method_38665(numberGetOrThrow(erosion)), + MultiNoiseUtil.method_38665(numberGetOrThrow(depth)), + MultiNoiseUtil.method_38665(numberGetOrThrow(weirdness)) + ); + biome = mnbs.getBiomeAtPoint(point); + Identifier biomeId = cc.s.getServer().getRegistryManager().get(Registry.BIOME_KEY).getId(biome); + return new StringValue(NBTSerializableValue.nameFromRegistryId(biomeId)); + } + + BlockArgument locator = BlockArgument.findIn(cc, lv, 0, false, false, true); + if (locator.replacement != null) { biome = world.getRegistryManager().get(Registry.BIOME_KEY).get(InputValidator.identifierOf(locator.replacement)); - if (biome == null) throw new ThrowStatement(locator.replacement, Throwables.UNKNOWN_BIOME) ; + if (biome == null) throw new ThrowStatement(locator.replacement, Throwables.UNKNOWN_BIOME); } else { @@ -1156,7 +1212,8 @@ public class WorldAccess { } String biomeFeature = lv.get(locator.offset).getString(); BiFunction featureProvider = BiomeInfo.biomeFeatures.get(biomeFeature); - if (featureProvider == null) throw new InternalExpressionException("Unknown biome feature: "+biomeFeature); + if (featureProvider == null) + throw new InternalExpressionException("Unknown biome feature: " + biomeFeature); return featureProvider.apply(world, biome); }); @@ -1512,4 +1569,23 @@ public class WorldAccess { }); } + + @ScarpetFunction(maxParams = -1) + public Value query_noise(Context c, @Locator.Block BlockPos pos, String... noiseQueries) { + int mappedX = BiomeCoords.fromBlock(pos.getX()); + int mappedY = BiomeCoords.fromBlock(pos.getY()); + int mappedZ = BiomeCoords.fromBlock(pos.getZ()); + MultiNoiseUtil.MultiNoiseSampler mns = ((CarpetContext) c).s.getWorld().getChunkManager().getChunkGenerator().getMultiNoiseSampler(); + Map ret = new HashMap<>(); + + if (noiseQueries.length == 0) { + noiseQueries = new String[]{"continentalness", "erosion", "weirdness", "temperature", "humidity", "depth"}; + } + + for (String noise : noiseQueries) { + double noiseValue = ((NoiseColumnSamplerInterface) mns).getNoiseSample(noise, mappedX, mappedY, mappedZ); + ret.put(new StringValue(noise), new NumericValue(noiseValue)); + } + return MapValue.wrap(ret); + } } diff --git a/src/main/resources/carpet.accesswidener b/src/main/resources/carpet.accesswidener index f335440e..fb8f780d 100644 --- a/src/main/resources/carpet.accesswidener +++ b/src/main/resources/carpet.accesswidener @@ -4,3 +4,4 @@ accessible class net/minecraft/server/world/ThreadedAnvilChunkStorage$TicketMana accessible class net/minecraft/server/world/ServerLightingProvider$Stage accessible class net/minecraft/recipe/Ingredient$Entry accessible class net/minecraft/world/chunk/light/BlockLightStorage$Data +accessible class net/minecraft/world/gen/NoiseColumnSampler$CaveScaler diff --git a/src/main/resources/carpet.mixins.json b/src/main/resources/carpet.mixins.json index b6b45795..4ccacbc7 100644 --- a/src/main/resources/carpet.mixins.json +++ b/src/main/resources/carpet.mixins.json @@ -204,6 +204,8 @@ "StatePredicate_scarpetMixin", "TagPredicate_scarpetMixin", "HorseBaseEntity_scarpetMixin", + "NoiseColumnSamplerMixin_scarpetMixin", + "CaveScalerMixin_scarpetMixin", "BlockEntity_movableBEMixin", "PistonBlock_movableTEMixin",