538 lines
18 KiB
Java
538 lines
18 KiB
Java
package net.minecraft.world.level.biome;
|
|
|
|
import com.google.common.annotations.VisibleForTesting;
|
|
import com.google.common.collect.ImmutableList;
|
|
import com.google.common.collect.Lists;
|
|
import com.mojang.datafixers.util.Pair;
|
|
import com.mojang.serialization.Codec;
|
|
import com.mojang.serialization.DataResult;
|
|
import com.mojang.serialization.MapCodec;
|
|
import com.mojang.serialization.codecs.RecordCodecBuilder;
|
|
import java.util.ArrayList;
|
|
import java.util.Arrays;
|
|
import java.util.Comparator;
|
|
import java.util.Iterator;
|
|
import java.util.List;
|
|
import java.util.Locale;
|
|
import java.util.stream.Collectors;
|
|
import net.minecraft.core.BlockPos;
|
|
import net.minecraft.core.QuartPos;
|
|
import net.minecraft.util.ExtraCodecs;
|
|
import net.minecraft.util.Mth;
|
|
import net.minecraft.world.level.levelgen.DensityFunction;
|
|
import net.minecraft.world.level.levelgen.DensityFunctions;
|
|
import org.jetbrains.annotations.Nullable;
|
|
|
|
public class Climate {
|
|
private static final boolean DEBUG_SLOW_BIOME_SEARCH = false;
|
|
private static final float QUANTIZATION_FACTOR = 10000.0F;
|
|
@VisibleForTesting
|
|
protected static final int PARAMETER_COUNT = 7;
|
|
|
|
public static Climate.TargetPoint target(float temperature, float humidity, float continentalness, float erosion, float depth, float weirdness) {
|
|
return new Climate.TargetPoint(
|
|
quantizeCoord(temperature), quantizeCoord(humidity), quantizeCoord(continentalness), quantizeCoord(erosion), quantizeCoord(depth), quantizeCoord(weirdness)
|
|
);
|
|
}
|
|
|
|
public static Climate.ParameterPoint parameters(
|
|
float temperature, float humidity, float continentalness, float erosion, float depth, float weirdness, float offset
|
|
) {
|
|
return new Climate.ParameterPoint(
|
|
Climate.Parameter.point(temperature),
|
|
Climate.Parameter.point(humidity),
|
|
Climate.Parameter.point(continentalness),
|
|
Climate.Parameter.point(erosion),
|
|
Climate.Parameter.point(depth),
|
|
Climate.Parameter.point(weirdness),
|
|
quantizeCoord(offset)
|
|
);
|
|
}
|
|
|
|
public static Climate.ParameterPoint parameters(
|
|
Climate.Parameter temperature,
|
|
Climate.Parameter humidity,
|
|
Climate.Parameter continentalness,
|
|
Climate.Parameter erosion,
|
|
Climate.Parameter depth,
|
|
Climate.Parameter weirdness,
|
|
float offset
|
|
) {
|
|
return new Climate.ParameterPoint(temperature, humidity, continentalness, erosion, depth, weirdness, quantizeCoord(offset));
|
|
}
|
|
|
|
public static long quantizeCoord(float coord) {
|
|
return (long)(coord * 10000.0F);
|
|
}
|
|
|
|
public static float unquantizeCoord(long coord) {
|
|
return (float)coord / 10000.0F;
|
|
}
|
|
|
|
public static Climate.Sampler empty() {
|
|
DensityFunction densityFunction = DensityFunctions.zero();
|
|
return new Climate.Sampler(densityFunction, densityFunction, densityFunction, densityFunction, densityFunction, densityFunction, List.of());
|
|
}
|
|
|
|
public static BlockPos findSpawnPosition(List<Climate.ParameterPoint> points, Climate.Sampler sampler) {
|
|
return (new Climate.SpawnFinder(points, sampler)).result.location();
|
|
}
|
|
|
|
interface DistanceMetric<T> {
|
|
long distance(Climate.RTree.Node<T> node, long[] ls);
|
|
}
|
|
|
|
public record Parameter(long min, long max) {
|
|
public static final Codec<Climate.Parameter> CODEC = ExtraCodecs.intervalCodec(
|
|
Codec.floatRange(-2.0F, 2.0F),
|
|
"min",
|
|
"max",
|
|
(float_, float2) -> float_.compareTo(float2) > 0
|
|
? DataResult.error(() -> "Cannon construct interval, min > max (" + float_ + " > " + float2 + ")")
|
|
: DataResult.success(new Climate.Parameter(Climate.quantizeCoord(float_), Climate.quantizeCoord(float2))),
|
|
parameter -> Climate.unquantizeCoord(parameter.min()),
|
|
parameter -> Climate.unquantizeCoord(parameter.max())
|
|
);
|
|
|
|
public static Climate.Parameter point(float value) {
|
|
return span(value, value);
|
|
}
|
|
|
|
public static Climate.Parameter span(float min, float max) {
|
|
if (min > max) {
|
|
throw new IllegalArgumentException("min > max: " + min + " " + max);
|
|
} else {
|
|
return new Climate.Parameter(Climate.quantizeCoord(min), Climate.quantizeCoord(max));
|
|
}
|
|
}
|
|
|
|
public static Climate.Parameter span(Climate.Parameter min, Climate.Parameter max) {
|
|
if (min.min() > max.max()) {
|
|
throw new IllegalArgumentException("min > max: " + min + " " + max);
|
|
} else {
|
|
return new Climate.Parameter(min.min(), max.max());
|
|
}
|
|
}
|
|
|
|
public String toString() {
|
|
return this.min == this.max ? String.format(Locale.ROOT, "%d", this.min) : String.format(Locale.ROOT, "[%d-%d]", this.min, this.max);
|
|
}
|
|
|
|
public long distance(long pointValue) {
|
|
long l = pointValue - this.max;
|
|
long m = this.min - pointValue;
|
|
return l > 0L ? l : Math.max(m, 0L);
|
|
}
|
|
|
|
public long distance(Climate.Parameter parameter) {
|
|
long l = parameter.min() - this.max;
|
|
long m = this.min - parameter.max();
|
|
return l > 0L ? l : Math.max(m, 0L);
|
|
}
|
|
|
|
public Climate.Parameter span(@Nullable Climate.Parameter param) {
|
|
return param == null ? this : new Climate.Parameter(Math.min(this.min, param.min()), Math.max(this.max, param.max()));
|
|
}
|
|
}
|
|
|
|
public static class ParameterList<T> {
|
|
private final List<Pair<Climate.ParameterPoint, T>> values;
|
|
private final Climate.RTree<T> index;
|
|
|
|
public static <T> Codec<Climate.ParameterList<T>> codec(MapCodec<T> codec) {
|
|
return ExtraCodecs.nonEmptyList(
|
|
RecordCodecBuilder.<T>create(
|
|
instance -> instance.group(Climate.ParameterPoint.CODEC.fieldOf("parameters").forGetter(Pair::getFirst), codec.forGetter(Pair::getSecond))
|
|
.apply(instance, Pair::of)
|
|
)
|
|
.listOf()
|
|
)
|
|
.xmap(Climate.ParameterList::new, Climate.ParameterList::values);
|
|
}
|
|
|
|
public ParameterList(List<Pair<Climate.ParameterPoint, T>> values) {
|
|
this.values = values;
|
|
this.index = Climate.RTree.create(values);
|
|
}
|
|
|
|
public List<Pair<Climate.ParameterPoint, T>> values() {
|
|
return this.values;
|
|
}
|
|
|
|
public T findValue(Climate.TargetPoint targetPoint) {
|
|
return this.findValueIndex(targetPoint);
|
|
}
|
|
|
|
@VisibleForTesting
|
|
public T findValueBruteForce(Climate.TargetPoint targetPoint) {
|
|
Iterator<Pair<Climate.ParameterPoint, T>> iterator = this.values().iterator();
|
|
Pair<Climate.ParameterPoint, T> pair = (Pair<Climate.ParameterPoint, T>)iterator.next();
|
|
long l = pair.getFirst().fitness(targetPoint);
|
|
T object = pair.getSecond();
|
|
|
|
while (iterator.hasNext()) {
|
|
Pair<Climate.ParameterPoint, T> pair2 = (Pair<Climate.ParameterPoint, T>)iterator.next();
|
|
long m = pair2.getFirst().fitness(targetPoint);
|
|
if (m < l) {
|
|
l = m;
|
|
object = pair2.getSecond();
|
|
}
|
|
}
|
|
|
|
return object;
|
|
}
|
|
|
|
public T findValueIndex(Climate.TargetPoint targetPoint) {
|
|
return this.findValueIndex(targetPoint, Climate.RTree.Node::distance);
|
|
}
|
|
|
|
protected T findValueIndex(Climate.TargetPoint targetPoint, Climate.DistanceMetric<T> distanceMetric) {
|
|
return this.index.search(targetPoint, distanceMetric);
|
|
}
|
|
}
|
|
|
|
public record ParameterPoint(
|
|
Climate.Parameter temperature,
|
|
Climate.Parameter humidity,
|
|
Climate.Parameter continentalness,
|
|
Climate.Parameter erosion,
|
|
Climate.Parameter depth,
|
|
Climate.Parameter weirdness,
|
|
long offset
|
|
) {
|
|
public static final Codec<Climate.ParameterPoint> CODEC = RecordCodecBuilder.create(
|
|
instance -> instance.group(
|
|
Climate.Parameter.CODEC.fieldOf("temperature").forGetter(parameterPoint -> parameterPoint.temperature),
|
|
Climate.Parameter.CODEC.fieldOf("humidity").forGetter(parameterPoint -> parameterPoint.humidity),
|
|
Climate.Parameter.CODEC.fieldOf("continentalness").forGetter(parameterPoint -> parameterPoint.continentalness),
|
|
Climate.Parameter.CODEC.fieldOf("erosion").forGetter(parameterPoint -> parameterPoint.erosion),
|
|
Climate.Parameter.CODEC.fieldOf("depth").forGetter(parameterPoint -> parameterPoint.depth),
|
|
Climate.Parameter.CODEC.fieldOf("weirdness").forGetter(parameterPoint -> parameterPoint.weirdness),
|
|
Codec.floatRange(0.0F, 1.0F).fieldOf("offset").xmap(Climate::quantizeCoord, Climate::unquantizeCoord).forGetter(parameterPoint -> parameterPoint.offset)
|
|
)
|
|
.apply(instance, Climate.ParameterPoint::new)
|
|
);
|
|
|
|
long fitness(Climate.TargetPoint point) {
|
|
return Mth.square(this.temperature.distance(point.temperature))
|
|
+ Mth.square(this.humidity.distance(point.humidity))
|
|
+ Mth.square(this.continentalness.distance(point.continentalness))
|
|
+ Mth.square(this.erosion.distance(point.erosion))
|
|
+ Mth.square(this.depth.distance(point.depth))
|
|
+ Mth.square(this.weirdness.distance(point.weirdness))
|
|
+ Mth.square(this.offset);
|
|
}
|
|
|
|
protected List<Climate.Parameter> parameterSpace() {
|
|
return ImmutableList.of(
|
|
this.temperature, this.humidity, this.continentalness, this.erosion, this.depth, this.weirdness, new Climate.Parameter(this.offset, this.offset)
|
|
);
|
|
}
|
|
}
|
|
|
|
protected static final class RTree<T> {
|
|
private static final int CHILDREN_PER_NODE = 6;
|
|
private final Climate.RTree.Node<T> root;
|
|
private final ThreadLocal<Climate.RTree.Leaf<T>> lastResult = new ThreadLocal();
|
|
|
|
private RTree(Climate.RTree.Node<T> root) {
|
|
this.root = root;
|
|
}
|
|
|
|
public static <T> Climate.RTree<T> create(List<Pair<Climate.ParameterPoint, T>> nodes) {
|
|
if (nodes.isEmpty()) {
|
|
throw new IllegalArgumentException("Need at least one value to build the search tree.");
|
|
} else {
|
|
int i = ((Climate.ParameterPoint)((Pair)nodes.get(0)).getFirst()).parameterSpace().size();
|
|
if (i != 7) {
|
|
throw new IllegalStateException("Expecting parameter space to be 7, got " + i);
|
|
} else {
|
|
List<Climate.RTree.Leaf<T>> list = (List<Climate.RTree.Leaf<T>>)nodes.stream()
|
|
.map(pair -> new Climate.RTree.Leaf<>((Climate.ParameterPoint)pair.getFirst(), pair.getSecond()))
|
|
.collect(Collectors.toCollection(ArrayList::new));
|
|
return new Climate.RTree<>(build(i, list));
|
|
}
|
|
}
|
|
}
|
|
|
|
private static <T> Climate.RTree.Node<T> build(int paramSpaceSize, List<? extends Climate.RTree.Node<T>> children) {
|
|
if (children.isEmpty()) {
|
|
throw new IllegalStateException("Need at least one child to build a node");
|
|
} else if (children.size() == 1) {
|
|
return (Climate.RTree.Node<T>)children.get(0);
|
|
} else if (children.size() <= 6) {
|
|
children.sort(Comparator.comparingLong(node -> {
|
|
long lx = 0L;
|
|
|
|
for (int jx = 0; jx < paramSpaceSize; jx++) {
|
|
Climate.Parameter parameter = node.parameterSpace[jx];
|
|
lx += Math.abs((parameter.min() + parameter.max()) / 2L);
|
|
}
|
|
|
|
return lx;
|
|
}));
|
|
return new Climate.RTree.SubTree<>(children);
|
|
} else {
|
|
long l = Long.MAX_VALUE;
|
|
int i = -1;
|
|
List<Climate.RTree.SubTree<T>> list = null;
|
|
|
|
for (int j = 0; j < paramSpaceSize; j++) {
|
|
sort(children, paramSpaceSize, j, false);
|
|
List<Climate.RTree.SubTree<T>> list2 = bucketize(children);
|
|
long m = 0L;
|
|
|
|
for (Climate.RTree.SubTree<T> subTree : list2) {
|
|
m += cost(subTree.parameterSpace);
|
|
}
|
|
|
|
if (l > m) {
|
|
l = m;
|
|
i = j;
|
|
list = list2;
|
|
}
|
|
}
|
|
|
|
sort(list, paramSpaceSize, i, true);
|
|
return new Climate.RTree.SubTree<>(
|
|
(List<? extends Climate.RTree.Node<T>>)list.stream().map(subTreex -> build(paramSpaceSize, Arrays.asList(subTreex.children))).collect(Collectors.toList())
|
|
);
|
|
}
|
|
}
|
|
|
|
private static <T> void sort(List<? extends Climate.RTree.Node<T>> children, int paramSpaceSize, int size, boolean absolute) {
|
|
Comparator<Climate.RTree.Node<T>> comparator = comparator(size, absolute);
|
|
|
|
for (int i = 1; i < paramSpaceSize; i++) {
|
|
comparator = comparator.thenComparing(comparator((size + i) % paramSpaceSize, absolute));
|
|
}
|
|
|
|
children.sort(comparator);
|
|
}
|
|
|
|
private static <T> Comparator<Climate.RTree.Node<T>> comparator(int size, boolean absolute) {
|
|
return Comparator.comparingLong(node -> {
|
|
Climate.Parameter parameter = node.parameterSpace[size];
|
|
long l = (parameter.min() + parameter.max()) / 2L;
|
|
return absolute ? Math.abs(l) : l;
|
|
});
|
|
}
|
|
|
|
private static <T> List<Climate.RTree.SubTree<T>> bucketize(List<? extends Climate.RTree.Node<T>> nodes) {
|
|
List<Climate.RTree.SubTree<T>> list = Lists.<Climate.RTree.SubTree<T>>newArrayList();
|
|
List<Climate.RTree.Node<T>> list2 = Lists.<Climate.RTree.Node<T>>newArrayList();
|
|
int i = (int)Math.pow(6.0, Math.floor(Math.log(nodes.size() - 0.01) / Math.log(6.0)));
|
|
|
|
for (Climate.RTree.Node<T> node : nodes) {
|
|
list2.add(node);
|
|
if (list2.size() >= i) {
|
|
list.add(new Climate.RTree.SubTree(list2));
|
|
list2 = Lists.<Climate.RTree.Node<T>>newArrayList();
|
|
}
|
|
}
|
|
|
|
if (!list2.isEmpty()) {
|
|
list.add(new Climate.RTree.SubTree(list2));
|
|
}
|
|
|
|
return list;
|
|
}
|
|
|
|
private static long cost(Climate.Parameter[] parameters) {
|
|
long l = 0L;
|
|
|
|
for (Climate.Parameter parameter : parameters) {
|
|
l += Math.abs(parameter.max() - parameter.min());
|
|
}
|
|
|
|
return l;
|
|
}
|
|
|
|
static <T> List<Climate.Parameter> buildParameterSpace(List<? extends Climate.RTree.Node<T>> children) {
|
|
if (children.isEmpty()) {
|
|
throw new IllegalArgumentException("SubTree needs at least one child");
|
|
} else {
|
|
int i = 7;
|
|
List<Climate.Parameter> list = Lists.<Climate.Parameter>newArrayList();
|
|
|
|
for (int j = 0; j < 7; j++) {
|
|
list.add(null);
|
|
}
|
|
|
|
for (Climate.RTree.Node<T> node : children) {
|
|
for (int k = 0; k < 7; k++) {
|
|
list.set(k, node.parameterSpace[k].span((Climate.Parameter)list.get(k)));
|
|
}
|
|
}
|
|
|
|
return list;
|
|
}
|
|
}
|
|
|
|
public T search(Climate.TargetPoint targetPoint, Climate.DistanceMetric<T> distanceMetric) {
|
|
long[] ls = targetPoint.toParameterArray();
|
|
Climate.RTree.Leaf<T> leaf = this.root.search(ls, (Climate.RTree.Leaf<T>)this.lastResult.get(), distanceMetric);
|
|
this.lastResult.set(leaf);
|
|
return leaf.value;
|
|
}
|
|
|
|
static final class Leaf<T> extends Climate.RTree.Node<T> {
|
|
final T value;
|
|
|
|
Leaf(Climate.ParameterPoint point, T value) {
|
|
super(point.parameterSpace());
|
|
this.value = value;
|
|
}
|
|
|
|
@Override
|
|
protected Climate.RTree.Leaf<T> search(long[] searchedValues, @Nullable Climate.RTree.Leaf<T> leaf, Climate.DistanceMetric<T> metric) {
|
|
return this;
|
|
}
|
|
}
|
|
|
|
abstract static class Node<T> {
|
|
protected final Climate.Parameter[] parameterSpace;
|
|
|
|
protected Node(List<Climate.Parameter> parameters) {
|
|
this.parameterSpace = (Climate.Parameter[])parameters.toArray(new Climate.Parameter[0]);
|
|
}
|
|
|
|
protected abstract Climate.RTree.Leaf<T> search(long[] searchedValues, @Nullable Climate.RTree.Leaf<T> leaf, Climate.DistanceMetric<T> metric);
|
|
|
|
protected long distance(long[] values) {
|
|
long l = 0L;
|
|
|
|
for (int i = 0; i < 7; i++) {
|
|
l += Mth.square(this.parameterSpace[i].distance(values[i]));
|
|
}
|
|
|
|
return l;
|
|
}
|
|
|
|
public String toString() {
|
|
return Arrays.toString(this.parameterSpace);
|
|
}
|
|
}
|
|
|
|
static final class SubTree<T> extends Climate.RTree.Node<T> {
|
|
final Climate.RTree.Node<T>[] children;
|
|
|
|
protected SubTree(List<? extends Climate.RTree.Node<T>> parameters) {
|
|
this(Climate.RTree.buildParameterSpace(parameters), parameters);
|
|
}
|
|
|
|
protected SubTree(List<Climate.Parameter> parameters, List<? extends Climate.RTree.Node<T>> children) {
|
|
super(parameters);
|
|
this.children = (Climate.RTree.Node<T>[])children.toArray(new Climate.RTree.Node[0]);
|
|
}
|
|
|
|
@Override
|
|
protected Climate.RTree.Leaf<T> search(long[] searchedValues, @Nullable Climate.RTree.Leaf<T> leaf, Climate.DistanceMetric<T> metric) {
|
|
long l = leaf == null ? Long.MAX_VALUE : metric.distance(leaf, searchedValues);
|
|
Climate.RTree.Leaf<T> leaf2 = leaf;
|
|
|
|
for (Climate.RTree.Node<T> node : this.children) {
|
|
long m = metric.distance(node, searchedValues);
|
|
if (l > m) {
|
|
Climate.RTree.Leaf<T> leaf3 = node.search(searchedValues, leaf2, metric);
|
|
long n = node == leaf3 ? m : metric.distance(leaf3, searchedValues);
|
|
if (l > n) {
|
|
l = n;
|
|
leaf2 = leaf3;
|
|
}
|
|
}
|
|
}
|
|
|
|
return leaf2;
|
|
}
|
|
}
|
|
}
|
|
|
|
public record Sampler(
|
|
DensityFunction temperature,
|
|
DensityFunction humidity,
|
|
DensityFunction continentalness,
|
|
DensityFunction erosion,
|
|
DensityFunction depth,
|
|
DensityFunction weirdness,
|
|
List<Climate.ParameterPoint> spawnTarget
|
|
) {
|
|
public Climate.TargetPoint sample(int x, int y, int z) {
|
|
int i = QuartPos.toBlock(x);
|
|
int j = QuartPos.toBlock(y);
|
|
int k = QuartPos.toBlock(z);
|
|
DensityFunction.SinglePointContext singlePointContext = new DensityFunction.SinglePointContext(i, j, k);
|
|
return Climate.target(
|
|
(float)this.temperature.compute(singlePointContext),
|
|
(float)this.humidity.compute(singlePointContext),
|
|
(float)this.continentalness.compute(singlePointContext),
|
|
(float)this.erosion.compute(singlePointContext),
|
|
(float)this.depth.compute(singlePointContext),
|
|
(float)this.weirdness.compute(singlePointContext)
|
|
);
|
|
}
|
|
|
|
public BlockPos findSpawnPosition() {
|
|
return this.spawnTarget.isEmpty() ? BlockPos.ZERO : Climate.findSpawnPosition(this.spawnTarget, this);
|
|
}
|
|
}
|
|
|
|
static class SpawnFinder {
|
|
Climate.SpawnFinder.Result result;
|
|
|
|
SpawnFinder(List<Climate.ParameterPoint> points, Climate.Sampler sampler) {
|
|
this.result = getSpawnPositionAndFitness(points, sampler, 0, 0);
|
|
this.radialSearch(points, sampler, 2048.0F, 512.0F);
|
|
this.radialSearch(points, sampler, 512.0F, 32.0F);
|
|
}
|
|
|
|
private void radialSearch(List<Climate.ParameterPoint> point, Climate.Sampler sampler, float max, float min) {
|
|
float f = 0.0F;
|
|
float g = min;
|
|
BlockPos blockPos = this.result.location();
|
|
|
|
while (g <= max) {
|
|
int i = blockPos.getX() + (int)(Math.sin(f) * g);
|
|
int j = blockPos.getZ() + (int)(Math.cos(f) * g);
|
|
Climate.SpawnFinder.Result result = getSpawnPositionAndFitness(point, sampler, i, j);
|
|
if (result.fitness() < this.result.fitness()) {
|
|
this.result = result;
|
|
}
|
|
|
|
f += min / g;
|
|
if (f > Math.PI * 2) {
|
|
f = 0.0F;
|
|
g += min;
|
|
}
|
|
}
|
|
}
|
|
|
|
private static Climate.SpawnFinder.Result getSpawnPositionAndFitness(List<Climate.ParameterPoint> points, Climate.Sampler sampler, int x, int z) {
|
|
double d = Mth.square(2500.0);
|
|
int i = 2;
|
|
long l = (long)(Mth.square(10000.0F) * Math.pow((Mth.square((long)x) + Mth.square((long)z)) / d, 2.0));
|
|
Climate.TargetPoint targetPoint = sampler.sample(QuartPos.fromBlock(x), 0, QuartPos.fromBlock(z));
|
|
Climate.TargetPoint targetPoint2 = new Climate.TargetPoint(
|
|
targetPoint.temperature(), targetPoint.humidity(), targetPoint.continentalness(), targetPoint.erosion(), 0L, targetPoint.weirdness()
|
|
);
|
|
long m = Long.MAX_VALUE;
|
|
|
|
for (Climate.ParameterPoint parameterPoint : points) {
|
|
m = Math.min(m, parameterPoint.fitness(targetPoint2));
|
|
}
|
|
|
|
return new Climate.SpawnFinder.Result(new BlockPos(x, 0, z), l + m);
|
|
}
|
|
|
|
record Result(BlockPos location, long fitness) {
|
|
}
|
|
}
|
|
|
|
public record TargetPoint(long temperature, long humidity, long continentalness, long erosion, long depth, long weirdness) {
|
|
|
|
@VisibleForTesting
|
|
protected long[] toParameterArray() {
|
|
return new long[]{this.temperature, this.humidity, this.continentalness, this.erosion, this.depth, this.weirdness, 0L};
|
|
}
|
|
}
|
|
}
|