Multi noise support for scarpet (#1176)

* fix: infinite params bug in AnnotationParser
feat: scarpet function query_noise()

* refactor: remove Method reflection

* fix: use BiomeCoords

* update: game version to 1.18-pre5

* feat: biome lookup from multinoise
fix: noise lookup wrongly scaled

* fix: jagged noise. Thanks, @Starmute

* fix: cave cheese and layer. Thanks, @Starmute

* fix: revert formatting
fix: caveLayer and caveCheese
feat: add terrain support

* more formatting reverts

* fix mapping issue

* use BlockPos

* docs: add documentation for query_noise

* docs: add documentation for biome(noise_map)
This commit is contained in:
Crec0 authored and GitHub committed 2021-11-30 02:47:28 +01:00
1 parent 6c7ac72c3e
commit aa57b586cb
8 files changed
+385 -6

No files matched your search

+27 -1
View File
@@ -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
</pre>
### `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.
<pre>
// 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}
</pre>
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)`
@@ -0,0 +1,5 @@
package carpet.fakes;
public interface NoiseColumnSamplerInterface {
double getNoiseSample(String name, int x, int y, int z);
}
@@ -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();
}
}
@@ -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);
}
}
@@ -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;
@@ -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)
@@ -1129,15 +1149,51 @@ public class WorldAccess {
return BooleanValue.of(blockLocator.block.getBlockState().isIn(blockTag));
});
expression.addContextFunction("biome", -1, (c, t, lv) -> {
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));
@@ -1156,7 +1212,8 @@ public class WorldAccess {
}
String biomeFeature = lv.get(locator.offset).getString();
BiFunction<ServerWorld, Biome, Value> 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<Value, Value> 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);
}
}
+1
View File
@@ -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
+2
View File
@@ -204,6 +204,8 @@
"StatePredicate_scarpetMixin",
"TagPredicate_scarpetMixin",
"HorseBaseEntity_scarpetMixin",
"NoiseColumnSamplerMixin_scarpetMixin",
"CaveScalerMixin_scarpetMixin",
"BlockEntity_movableBEMixin",
"PistonBlock_movableTEMixin",