Add Hallelujah Mountains support

The Hallelujah Mountains mod (aka cloudlands) was designed in Amidstest, this is its code.
master
Treer 2018-06-16 23:07:16 +10:00
parent 866af79cb8
commit 2173935d81
7 changed files with 504 additions and 12 deletions

View File

@ -166,7 +166,7 @@ public class Drawer {
private void clear() {
if (dimensionSetting.get().equals(Dimension.END)) {
g2d.setPaint(voidTexturePaint);
} else if (worldType == WorldType.V7_FLOATLANDS) { // consider making "isAboveVoid" a field of WorldType if it ever applies to more than one worldtype
} else if (worldType == WorldType.V7_FLOATLANDS || worldType == WorldType.HALLELUJAH_MOUNTAINS) { // consider making "isAboveVoid" a field of WorldType if it ever applies to more than one worldtype
g2d.setPaint(void2TexturePaint);
} else {
g2d.setColor(Color.black);

View File

@ -59,6 +59,7 @@ public class DefaultVersionFeatures implements VersionFeaturesFactory {
WorldType.V5,
WorldType.CARPATHIAN,
WorldType.FLAT,
WorldType.HALLELUJAH_MOUNTAINS,
//WorldType.FRACTAL
}
);
@ -71,6 +72,14 @@ public class DefaultVersionFeatures implements VersionFeaturesFactory {
});
enabledLayers = new HashMap<WorldType, VersionFeature<List<Integer>>>();
enabledLayers.put(WorldType.HALLELUJAH_MOUNTAINS,
VersionFeature.<Integer> listBuilder()
.init(commonLayers)
.initExtend(
LayerIds.MINETEST_OCEAN
//,LayerIds.MINETEST_MOUNTAIN
).construct()
);
enabledLayers.put(WorldType.CARPATHIAN,
VersionFeature.<Integer> listBuilder()
.init(commonLayers)
@ -126,6 +135,10 @@ public class DefaultVersionFeatures implements VersionFeaturesFactory {
// It had to be this way because of how VersionFeatures for Minecraft are handled
this.biomeDataOracle = VersionFeature.<WorldType, TriFunction<Long, MapgenParams, BiomeProfileSelection, IBiomeDataOracle>> mapBuilder()
.init(
new AbstractMap.SimpleEntry<WorldType, TriFunction<Long, MapgenParams, BiomeProfileSelection, IBiomeDataOracle>>(
WorldType.HALLELUJAH_MOUNTAINS,
(seed, mapgenParams, biomeProfile) -> new amidst.minetest.world.oracle.BiomeDataOracleHallelujah(mapgenParams, biomeProfile, seed)
),
new AbstractMap.SimpleEntry<WorldType, TriFunction<Long, MapgenParams, BiomeProfileSelection, IBiomeDataOracle>>(
WorldType.V7,
(seed, mapgenParams, biomeProfile) -> new amidst.minetest.world.oracle.BiomeDataOracleV7(false, mapgenParams, biomeProfile, seed)

View File

@ -0,0 +1,61 @@
package amidst.minetest.world.mapgen;
import javax.vecmath.Vector3f;
import amidst.mojangapi.world.WorldType;
// TODO: be able to import these values from minetest.conf
public class MapgenHallelujahParams extends MapgenParams {
public class CoreSize {
public int territorySize;
public int coresPerTerritory;
public int maxRadius;
public int maxThickness;
public float frequency;
public boolean requiresNexus;
public boolean exclusive;
public float pondWallBuffer;
public CoreSize(int territory_size, int cores_per_territory, int max_radius, int max_thickness, float frequency, float pond_wall_buffer, boolean requires_nexus, boolean exclusive) {
this.territorySize = territory_size;
this.coresPerTerritory = cores_per_territory;
this.maxRadius = max_radius;
this.maxThickness = max_thickness;
this.frequency = frequency;
this.requiresNexus = requires_nexus;
this.exclusive = exclusive;
this.pondWallBuffer = pond_wall_buffer;
}
}
public CoreSize[] cores = new CoreSize[] {
new CoreSize(200, 3, 96, 8, 0.1f, 0.03f, true, false),
new CoreSize(60, 1, 40, 4, 0.1f, 0.06f, false, true),
new CoreSize(30, 3, 16, 3, 0.1f, 0.11f, false, true)
};
public short cloudlands_altitude = 200;
//public float cloudlands_altitude_amplitude = 30f; // not needed for 2d map
public float required_density = 0.4f;
public NoiseParams np_eddyField = new NoiseParams( -1f, 2, new Vector3f(350, 350, 350), 1000, (short)2, 0.7f, 2f);
public NoiseParams np_surfaceMap = new NoiseParams(0.5f, 0.5f, new Vector3f( 40, 40, 40), 1000, (short)4, 0.5f, 2f);
public NoiseParams np_density = new NoiseParams(0.7f, 0.3f, new Vector3f( 25, 25, 25), 1000, (short)4, 0.5f, 2f);
@Override
public String toString() {
String prefix = "cloudlands_";
StringBuilder result = new StringBuilder();
result.append(super.toString());
result.append(np_eddyField.toString( prefix + "np_eddyField"));
result.append(np_surfaceMap.toString(prefix + "np_surfaceMap"));
result.append(np_density.toString( prefix + "np_density"));
return result.toString();
}
@Override
public WorldType getWorldType() {
return WorldType.HALLELUJAH_MOUNTAINS;
}
}

View File

@ -1,5 +1,8 @@
package amidst.minetest.world.mapgen;
/**
* Copy of the noise functions from Minetest - should produce exact same results
*/
public class Noise {
public static final int FLAG_DEFAULTS = 0x01;

View File

@ -0,0 +1,100 @@
package amidst.minetest.world.mapgen;
/**
* Copy of Minetest's implementation of PcgRandom
*/
public class PcgRandom {
public static final int RANDOM_MIN = -0x7fffffff - 1;
public static final int RANDOM_MAX = 0x7fffffff;
public static final int RANDOM_RANGE = 0xffffffff;
/**
* m_state and m_inc are unsigned long ints, which Java does not possess, so
* divide and shift operations on them must be performed unsigned
*
* Note that in two's complement arithmetic, the arithmetic operations of
* add, subtract, and multiply are bit-wise identical if the two operands
* are regarded as both being signed or both being unsigned.
*/
private long m_state;
private long m_inc;
public int range(int min, int max)
{
if (max < min)
throw new RuntimeException("Invalid range (max < min)");
// We have to cast to s64 because otherwise this could overflow,
// and signed overflow is undefined behavior.
int bound = (int)((long)max - (long)min + 1);
return range(bound) + min;
}
public int range(int bound)
{
// If the bound is 0, we cover the whole RNG's range
if (bound == 0)
return next();
/*
This is an optimization of the expression:
0x100000000ull % bound
since 64-bit modulo operations typically much slower than 32.
int threshold = -bound % bound;
*/
// Skipping that optimization, as it doesn't work in Java with signed 32bit ints
int threshold = (int)(0x100000000L % (long)bound);
long r; // long to prevent values above 2^31 being treated as < threshold
/*
If the bound is not a multiple of the RNG's range, it may cause bias,
e.g. a RNG has a range from 0 to 3 and we take want a number 0 to 2.
Using rand() % 3, the number 0 would be twice as likely to appear.
With a very large RNG range, the effect becomes less prevalent but
still present.
This can be solved by modifying the range of the RNG to become a
multiple of bound by dropping values above the a threshold.
In our example, threshold == 4 % 3 == 1, so reject values < 1
(that is, 0), thus making the range == 3 with no bias.
This loop may look dangerous, but will always terminate due to the
RNG's property of uniformity.
*/
while ((r = next() & 0xFFFFFFFFL) < threshold)
;
return (int)(r % bound);
}
public int lua_next() {
return range(RANDOM_MIN, RANDOM_MAX);
}
public int next()
{
long oldstate = m_state;
m_state = oldstate * 6364136223846793005L + m_inc;
int xorshifted = (int)(((oldstate >>> 18) ^ oldstate) >>> 27);
int rot = (int)(oldstate >>> 59);
//return (xorshifted >>> rot) | (xorshifted << ((-rot) & 31));
return Integer.rotateRight(xorshifted, rot);
}
private void seed(long state, long seq) {
m_state = 0;
m_inc = (seq << 1L) | 1L;
next();
m_state += state;
next();
}
public PcgRandom(long state) {
// u64 state=0x853c49e6748fea9bULL, u64 seq=0xda3e39cb94b95bdbULL)
seed(state, 0xda3e39cb94b95bdbL );
}
}

View File

@ -0,0 +1,314 @@
package amidst.minetest.world.oracle;
import java.util.ArrayList;
import java.util.List;
import amidst.documentation.Immutable;
import amidst.logging.AmidstLogger;
import amidst.logging.AmidstMessageBox;
import amidst.minetest.world.mapgen.Constants;
import amidst.minetest.world.mapgen.InvalidNoiseParamsException;
import amidst.minetest.world.mapgen.MapgenHallelujahParams;
import amidst.minetest.world.mapgen.MapgenHallelujahParams.CoreSize;
import amidst.minetest.world.mapgen.MapgenParams;
import amidst.minetest.world.mapgen.MinetestBiome;
import amidst.minetest.world.mapgen.Noise;
import amidst.minetest.world.mapgen.PcgRandom;
import amidst.mojangapi.world.coordinates.CoordinatesInWorld;
import amidst.mojangapi.world.coordinates.Resolution;
import amidst.settings.biomeprofile.BiomeProfileSelection;
@Immutable
public class BiomeDataOracleHallelujah extends MinetestBiomeDataOracle {
private final MapgenHallelujahParams hallelujahParams;
class Core{
int x;
int y;
int z;
float radius;
float depth;
CoreSize type;
Boolean marked = null;
public Core(int x, int y, int z, float radius, float depth, CoreSize type) {
this.x = x;
this.y = y;
this.z = z;
this.radius = radius;
this.depth = depth;
this.type = type;
}
}
private Noise noise_density;
private Noise noise_eddyField;
private Noise noise_surfaceMap;
/**
* @param mapgenHallelujahParams
* @param biomeProfileSelection - if null then a default biomeprofile will be used
* @param seed
* @throws InvalidNoiseParamsException
*/
public BiomeDataOracleHallelujah(MapgenParams mapgenHallelujahParams, BiomeProfileSelection biomeProfileSelection, long seed) {
super(mapgenHallelujahParams, biomeProfileSelection, seed);
if (params instanceof MapgenHallelujahParams) {
hallelujahParams = (MapgenHallelujahParams)params;
} else {
AmidstLogger.error("Error: BiomeDataOracleHallelujah cannot cast params to HallelujahParams. Using defaults instead.");
this.params = hallelujahParams = new MapgenHallelujahParams();
}
try {
// 2D noise
noise_eddyField = new Noise(hallelujahParams.np_eddyField, this.seed, params.chunk_length_x, params.chunk_length_z);
noise_surfaceMap = new Noise(hallelujahParams.np_surfaceMap, this.seed, params.chunk_length_x, params.chunk_length_z);
// 3D noise
noise_density = new Noise(hallelujahParams.np_density, this.seed, params.chunk_length_x, params.chunk_length_y, params.chunk_length_z);
} catch (InvalidNoiseParamsException ex) {
AmidstLogger.error("Invalid HallelujahParams from Minetest game. " + ex);
ex.printStackTrace();
}
}
private List<Core> findCores(List<Core> previous, CoreSize size, int x1, int z1, int x2, int z2) {
List<Core> result = new ArrayList<Core>();
PcgRandom prng;
List<Core> coresInTerritory = new ArrayList<Core>();
Noise noise = noise_eddyField;
float rotA = (float) Math.cos(-15 * Math.PI/180f);
float rotB = (float) Math.sin(-15 * Math.PI/180f);
for(int z = (int)Math.floor(z1 / (float)size.territorySize); z <= Math.floor(z2 / (float)size.territorySize); z++) {
for(int x = (int)Math.floor(x1 / (float)size.territorySize); x <= Math.floor(x2 / (float)size.territorySize); x++) {
coresInTerritory.clear();
prng = new PcgRandom(
x * 8973896 +
z * 7467838 +
seed + 9438
);
for(int i = 0; i < size.coresPerTerritory; i++) {
int coreX = x * size.territorySize + prng.range(0, size.territorySize - 1);
int coreZ = z * size.territorySize + prng.range(0, size.territorySize - 1);
float noiseX = rotA * coreX - rotB * coreZ;
float noiseZ = rotB * coreX + rotA * coreZ;
float tendrils = Noise.NoisePerlin2D(noise.np, noiseX, noiseZ, seed);
if (Math.abs(tendrils) < size.frequency) {
boolean nexus = !size.requiresNexus;
if (!nexus) {
float field1b = Noise.NoisePerlin2D(noise.np, noiseX + 2, noiseZ, seed);
float field1c = Noise.NoisePerlin2D(noise.np, noiseX, noiseZ + 2, seed);
if (Math.abs(tendrils - field1b) + Math.abs(tendrils - field1c) < 0.02) nexus = true;
}
if (nexus) {
float radius = (size.maxRadius + prng.range(0, size.maxRadius) * 2) / 3;
float depth = (size.maxRadius + prng.range(0, size.maxRadius) * 2) / 2;
int thickness = size.maxRadius + prng.range(0, size.maxThickness); // only needed to advance the PRNG
if (coreX >= x1 && coreX < x2 & coreZ >= z1 && coreZ < z2) {
boolean spaceAvailable = !size.exclusive;
if (!spaceAvailable) {
// see if any other cores occupy this space, and if so then
// either deny the core, or raise it
spaceAvailable = true;
float minDistSquared = radius * radius * .7f;
for(Core core: previous) {
if ((core.x - coreX)*(core.x - coreX) + (core.z - coreZ)*(core.z - coreZ) <= (minDistSquared + core.radius * core.radius)) {
spaceAvailable = false;
break;
}
}
if (spaceAvailable) for(Core core: coresInTerritory) {
if ((core.x - coreX)*(core.x - coreX) + (core.z - coreZ)*(core.z - coreZ) <= (minDistSquared + core.radius * core.radius)) {
spaceAvailable = false;
break;
}
}
}
if (spaceAvailable) {
Core newCore = new Core(
coreX,
hallelujahParams.cloudlands_altitude,
coreZ,
radius,
depth,
size
);
result.add(newCore);
coresInTerritory.add(newCore);
}
} else {
// We couldn't filter this core out earlier as that would break the determinism of the prng
}
}
}
}
}
}
return result;
}
private List<Core> getCores(int x1, int z1, int x2, int z2) {
List<Core> result = new ArrayList<Core>();
for(CoreSize size: hallelujahParams.cores) {
result.addAll(findCores(result, size, x1 - size.maxRadius, z1 - size.maxRadius, x2 + size.maxRadius, z2 + size.maxRadius));
}
return result;
}
@Override
public short populateArray(CoordinatesInWorld corner, short[][] result, boolean useQuarterResolution) {
int width = result.length;
if (width > 0) {
Resolution resolution = Resolution.from(useQuarterResolution);
int height = result[0].length;
int left = (int) corner.getX();
int top = (int) corner.getY();
int shift = resolution.getShift();
int step = resolution.getStep();
int world_z;
int world_x;
short biomeValue;
MinetestBiome[] biomes = getBiomeArray();
List<Core> cores = getCores(left, -(top + (height << shift)), left + (width << shift), -top);
try {
for (int y = 0; y < height; y++) {
world_z = top + (y << shift);
world_x = left;
// Use -world_z because Minetest uses left-handed coordinates, while Minecraft
// and Amidst use right-handed coordinates.
world_z = -world_z;
for (int x = 0; x < width; x++) {
biomeValue = 0;
short surface_y = Constants.MAX_MAP_GENERATION_LIMIT;
/* visualizations used in the design of the mod
float rotX = rotA * world_x - rotB * world_z;
float rotZ = rotB * world_x + rotA * world_z;
float field1 = Noise.NoisePerlin2D(noise_eddyField.np, rotX, rotZ, seed);
float field1b = Noise.NoisePerlin2D(noise_eddyField.np, rotX + 2, rotZ, seed);
float field1c = Noise.NoisePerlin2D(noise_eddyField.np, rotX, rotZ + 2, seed);
if (Math.abs(field1 - field1b) + Math.abs(field1 - field1c) < 0.01) biomeValue |= BITPLANE_MOUNTAIN;
if (Math.abs(field1) <= .1) {
biomeValue |= BITPLANE_RIVER;
} */
for (Core core : cores) {
float distanceSquared = (world_x - core.x) * (world_x - core.x) + (world_z - core.z) * (world_z - core.z);
float radiusSquared = core.radius * core.radius;
if (distanceSquared <= radiusSquared) {
float horz_easing;
float noise_weighting = 1.0f;
int shapeType = (int)Math.floor(core.depth + core.radius + core.x) % 5;
if (shapeType < 2) {
horz_easing = 1 - distanceSquared / radiusSquared;
} else if (shapeType == 2) {
horz_easing = (float)(1.0f - Math.sqrt(distanceSquared) / core.radius);
} else {
float squared = 1 - distanceSquared / radiusSquared;
float distance = (float) Math.sqrt(distanceSquared);
float distance_normalized = distance / core.radius;
float root = (float) (1 - Math.sqrt(distance) / Math.sqrt(core.radius));
horz_easing = (float) Math.min(1, 0.8*distance_normalized*squared + 1.2*(1-distance_normalized)*root);
noise_weighting = 0.63f;
}
if (core.radius + core.depth > 80) {
if (core.radius + core.depth > 120) {
noise_weighting = 0.35f;
} else {
noise_weighting = Math.min(0.6f, noise_weighting);
}
}
// sample density at two points to reduce holes in our island maps
float density1 = Noise.NoisePerlin3D(noise_density.np, world_x, 1, world_z, seed);
float density2 = Noise.NoisePerlin3D(noise_density.np, world_x, -3, world_z, seed);
density1 = noise_weighting * density1 + (1 - noise_weighting) * hallelujahParams.np_density.offset;
density2 = noise_weighting * density2 + (1 - noise_weighting) * hallelujahParams.np_density.offset;
float maxDensity = Math.max(density1, density2);
if (maxDensity * (horz_easing + 1) / 2 > hallelujahParams.required_density) {
if (Noise.NoisePerlin2D(noise_surfaceMap.np, world_x, world_z, seed) < 0) {
if (density1 * (horz_easing + 1) / 2 > hallelujahParams.required_density + core.type.pondWallBuffer) {
biomeValue |= BITPLANE_OCEAN;
}
}
surface_y = hallelujahParams.cloudlands_altitude;
if ((biomeValue & MASK_BITPLANES) == 0) {
// (mask the bitplanes in case the biome returned is -1 (NONE)
biomeValue = (short)(biomeValue & ~MASK_BITPLANES);
biomeValue |= calcBiomeAtPoint(biomes, core.x, surface_y, core.z).getIndex() & MASK_BITPLANES;
}
}
if ((core.marked == null || core.marked.booleanValue()) && ((x + y) & 1) == 0) {
// Mark cores (was used for tuning the mod)
if (core.marked == null) {
core.marked = new Boolean(false);
int territoryZ = (int)Math.floor(core.z / (float)core.type.territorySize);
int territoryX = (int)Math.floor(core.x / (float)core.type.territorySize);
if (((territoryX + territoryZ) % 2) == 0 && core.radius > 18 && core.depth > 20 && core.radius + core.depth > 60) {
float heat =
Noise.NoisePerlin2D(params.np_heat, core.x, core.z, seed) +
Noise.NoisePerlin2D(params.np_heat_blend, core.x, core.z, seed);
float humidity =
Noise.NoisePerlin2D(params.np_humidity, core.x, core.z, seed) +
Noise.NoisePerlin2D(params.np_humidity_blend, core.x, core.z, seed);
if ((heat < 5 && core.x % 3 == 0) || (heat > 50 && humidity > 60)) {
core.marked = new Boolean(true);
}
}
}
if (core.marked.booleanValue()) biomeValue |= BITPLANE_MOUNTAIN;
}
}
}
if (surface_y >= Constants.MAX_MAP_GENERATION_LIMIT) {
// It's a long fall, show the clouds/void
biomeValue = (short) MinetestBiome.VOID.getIndex();
}
result[x][y] = biomeValue;
world_x += step;
}
}
} catch (Exception e) {
AmidstLogger.error(e);
AmidstMessageBox.displayError("Error", e);
}
}
return MASK_BITPLANES;
}
}

View File

@ -8,20 +8,21 @@ import amidst.mojangapi.minecraftinterface.local.SymbolicNames;
@Immutable
public enum WorldType {
// @formatter:off
DEFAULT ("Default", "default", SymbolicNames.FIELD_WORLD_TYPE_DEFAULT),
FLAT ("Flat", "flat", SymbolicNames.FIELD_WORLD_TYPE_FLAT),
LARGE_BIOMES ("Large Biomes", "large-biomes", SymbolicNames.FIELD_WORLD_TYPE_LARGE_BIOMES),
AMPLIFIED ("Amplified", "amplified", SymbolicNames.FIELD_WORLD_TYPE_AMPLIFIED),
CUSTOMIZED ("Customized", "customized", SymbolicNames.FIELD_WORLD_TYPE_CUSTOMIZED),
DEFAULT ("Default", "default", SymbolicNames.FIELD_WORLD_TYPE_DEFAULT),
FLAT ("Flat", "flat", SymbolicNames.FIELD_WORLD_TYPE_FLAT),
LARGE_BIOMES ("Large Biomes", "large-biomes", SymbolicNames.FIELD_WORLD_TYPE_LARGE_BIOMES),
AMPLIFIED ("Amplified", "amplified", SymbolicNames.FIELD_WORLD_TYPE_AMPLIFIED),
CUSTOMIZED ("Customized", "customized", SymbolicNames.FIELD_WORLD_TYPE_CUSTOMIZED),
// Minetest world types
V5 ("v5", "v5", null),
V6 ("v6", "v6", null),
V7 ("v7", "v7", null),
V7_FLOATLANDS ("v7 Floatlands", "v7floatlands", null),
V5 ("v5", "v5", null),
V6 ("v6", "v6", null),
V7 ("v7", "v7", null),
V7_FLOATLANDS ("v7 Floatlands", "v7floatlands", null),
// FLAT - can reuse the minecraft FLAT
FRACTAL ("Fractal", "fractal", null),
CARPATHIAN ("Carpathian", "carpathian", null);
FRACTAL ("Fractal", "fractal", null),
CARPATHIAN ("Carpathian", "carpathian", null),
HALLELUJAH_MOUNTAINS ("Hallelujah Mountains (mod)", "hallelujahmountains", null);
// @formatter:on
public static final String PROMPT_EACH_TIME = "Prompt each time";