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