Current iteration of work.

Attempted at fixing the coordinate converter again.

Added more applicable tests for them.

Changing to a more stateless design for biome generator.

Refactored correcting package name to fit conventions.
This commit is contained in:
Harrison Deng 2020-04-27 14:23:57 -05:00
parent 59d78c9754
commit 1e8dc8019a
18 changed files with 315 additions and 434 deletions

View File

@ -3,15 +3,14 @@ package ca.recrown.islandsurvivalcraft;
import org.bukkit.generator.ChunkGenerator; import org.bukkit.generator.ChunkGenerator;
import org.bukkit.plugin.java.JavaPlugin; import org.bukkit.plugin.java.JavaPlugin;
import ca.recrown.islandsurvivalcraft.world.generation.BiomePerIslandGenerator; import ca.recrown.islandsurvivalcraft.world.generation.IslandWorldChunkGenerator;
import ca.recrown.islandsurvivalcraft.world.generation.alternation.AlternatingChunkGenerator;
public class IslandSurvivalCraft extends JavaPlugin { public class IslandSurvivalCraft extends JavaPlugin {
AlternatingChunkGenerator generator; private IslandWorldChunkGenerator chunkGenerator = new IslandWorldChunkGenerator();
@Override @Override
public void onEnable() { public void onEnable() {
generator = new AlternatingChunkGenerator(this, new BiomePerIslandGenerator());
super.onEnable(); super.onEnable();
} }
@ -22,6 +21,6 @@ public class IslandSurvivalCraft extends JavaPlugin {
@Override @Override
public ChunkGenerator getDefaultWorldGenerator(String worldName, String id) { public ChunkGenerator getDefaultWorldGenerator(String worldName, String id) {
return generator; return chunkGenerator;
} }
} }

View File

@ -1,4 +1,4 @@
package ca.recrown.islandsurvivalcraft.Types; package ca.recrown.islandsurvivalcraft.types;
import java.util.Objects; import java.util.Objects;

View File

@ -4,7 +4,7 @@ import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map.Entry; import java.util.Map.Entry;
import ca.recrown.islandsurvivalcraft.Types.Point2; import ca.recrown.islandsurvivalcraft.types.Point2;
public class Utilities { public class Utilities {
public final static int CHUNK_SIZE = 16; public final static int CHUNK_SIZE = 16;
@ -53,19 +53,17 @@ public class Utilities {
public static Point2 worldToLocalChunkCoordinates(int x, int y) { public static Point2 worldToLocalChunkCoordinates(int x, int y) {
int xRes = 0; int xRes = 0;
if (x < 0) { if (x < 0) {
xRes = CHUNK_SIZE - 1 + (x % CHUNK_SIZE); xRes = CHUNK_SIZE + (x % CHUNK_SIZE);
xRes %= CHUNK_SIZE - 1; xRes %= CHUNK_SIZE;
} else { } else {
xRes = x % CHUNK_SIZE; xRes = x % CHUNK_SIZE;
if (xRes > 0) xRes -= 1;
} }
int yRes = 0; int yRes = 0;
if (y < 0) { if (y < 0) {
yRes = CHUNK_SIZE - 1 + (y % CHUNK_SIZE); yRes = CHUNK_SIZE + (y % CHUNK_SIZE);
yRes %= CHUNK_SIZE - 1; yRes %= CHUNK_SIZE;
} else { } else {
yRes = y % CHUNK_SIZE; yRes = y % CHUNK_SIZE;
if (yRes > 0) yRes -= 1;
} }
return new Point2(xRes, yRes); return new Point2(xRes, yRes);
} }

View File

@ -2,7 +2,7 @@ package ca.recrown.islandsurvivalcraft.pathfinding;
import java.util.Objects; import java.util.Objects;
import ca.recrown.islandsurvivalcraft.Types.Point2; import ca.recrown.islandsurvivalcraft.types.Point2;
public class DFSNode extends Point2 implements Comparable<DFSNode> { public class DFSNode extends Point2 implements Comparable<DFSNode> {
private DFSNode goal; private DFSNode goal;

View File

@ -6,7 +6,7 @@ import java.util.Queue;
import org.apache.commons.lang.NullArgumentException; import org.apache.commons.lang.NullArgumentException;
import ca.recrown.islandsurvivalcraft.Types.Point2; import ca.recrown.islandsurvivalcraft.types.Point2;
public class DepthFirstSearch { public class DepthFirstSearch {
private Queue<DFSNode> queue; private Queue<DFSNode> queue;

View File

@ -117,6 +117,8 @@ public class BiomeSelector {
* @return the Minecraft temperature. * @return the Minecraft temperature.
*/ */
public float getBiomeTemperature(Biome biome) { public float getBiomeTemperature(Biome biome) {
if (!initialized) throw new IllegalStateException("Biome selector is not initialized.");
String biomeName = biome.name().toLowerCase(); String biomeName = biome.name().toLowerCase();
if (biomeName.endsWith("hills") || biomeName.endsWith("beach") || biomeName.endsWith("shore")) { if (biomeName.endsWith("hills") || biomeName.endsWith("beach") || biomeName.endsWith("shore")) {
String biomeTypeName = biomeName; String biomeTypeName = biomeName;
@ -140,6 +142,8 @@ public class BiomeSelector {
* @return the resulting transition biome. * @return the resulting transition biome.
*/ */
public Biome getTransitionalBiome(Biome from) { public Biome getTransitionalBiome(Biome from) {
if (!initialized) throw new IllegalStateException("Biome selector is not initialized.");
String biomeName = from.name().toLowerCase(); String biomeName = from.name().toLowerCase();
if (biomeName.contains("jungle")) { if (biomeName.contains("jungle")) {
if (biomeName.contains("modified")) { if (biomeName.contains("modified")) {
@ -160,6 +164,8 @@ public class BiomeSelector {
* @return the shore biome associated with it. * @return the shore biome associated with it.
*/ */
public Biome getShoreBiome(Biome from, float temperature) { public Biome getShoreBiome(Biome from, float temperature) {
if (!initialized) throw new IllegalStateException("Biome selector is not initialized.");
String biomeName = from.name().toLowerCase(); String biomeName = from.name().toLowerCase();
if (biomeName.contains("mushroom")) { if (biomeName.contains("mushroom")) {
return Biome.MUSHROOM_FIELD_SHORE; return Biome.MUSHROOM_FIELD_SHORE;
@ -173,10 +179,12 @@ public class BiomeSelector {
} }
public boolean isOceanBiome(Biome biome) { public boolean isOceanBiome(Biome biome) {
if (!initialized) throw new IllegalStateException("Biome selector is not initialized.");
return oceans.containsKey(biome); return oceans.containsKey(biome);
} }
public boolean isLandBiome(Biome biome) { public boolean isLandBiome(Biome biome) {
if (!initialized) throw new IllegalStateException("Biome selector is not initialized.");
return !isOceanBiome(biome); return !isOceanBiome(biome);
} }
@ -186,6 +194,7 @@ public class BiomeSelector {
* @return The randomly selected biome. * @return The randomly selected biome.
*/ */
public Biome getLandBiome(float temperature) { public Biome getLandBiome(float temperature) {
if (!initialized) throw new IllegalStateException("Biome selector is not initialized.");
ArrayList<Biome> biomes = null; ArrayList<Biome> biomes = null;
if (temperature <= 0.05f) { if (temperature <= 0.05f) {
biomes = temperaturePartitionedLandBiomes.get(0.05f); biomes = temperaturePartitionedLandBiomes.get(0.05f);
@ -206,6 +215,7 @@ public class BiomeSelector {
* @return The randomly selected biome. * @return The randomly selected biome.
*/ */
public Biome getOceanBiome(float temperature) { public Biome getOceanBiome(float temperature) {
if (!initialized) throw new IllegalStateException("Biome selector is not initialized.");
ArrayList<Biome> biomes = null; ArrayList<Biome> biomes = null;
if (temperature <= 0.00f) { if (temperature <= 0.00f) {
biomes = temperaturePartitionedOceanBiomes.get(0.0f); biomes = temperaturePartitionedOceanBiomes.get(0.0f);

View File

@ -4,7 +4,7 @@ import java.util.Random;
import org.bukkit.util.noise.SimplexOctaveGenerator; import org.bukkit.util.noise.SimplexOctaveGenerator;
import ca.recrown.islandsurvivalcraft.Types.Point2; import ca.recrown.islandsurvivalcraft.types.Point2;
import ca.recrown.islandsurvivalcraft.caching.Cache; import ca.recrown.islandsurvivalcraft.caching.Cache;
import ca.recrown.islandsurvivalcraft.pathfinding.CoordinateValidatable; import ca.recrown.islandsurvivalcraft.pathfinding.CoordinateValidatable;
import ca.recrown.islandsurvivalcraft.pathfinding.DepthFirstSearch; import ca.recrown.islandsurvivalcraft.pathfinding.DepthFirstSearch;

View File

@ -1,25 +0,0 @@
package ca.recrown.islandsurvivalcraft.world.generation;
import java.util.Random;
import org.bukkit.util.noise.SimplexOctaveGenerator;
public class BedrockGenerator {
private final SimplexOctaveGenerator noiseGenerator;
private final int maxBedrockHeight;
private final int minBedrockHeight;
public BedrockGenerator(Random random, int maxBedrockHeight, int minBedrockHeight) {
noiseGenerator = new SimplexOctaveGenerator(random, 1);
noiseGenerator.setScale(0.1D);
this.maxBedrockHeight = maxBedrockHeight;
this.minBedrockHeight = minBedrockHeight;
}
private double getNormalizedNoise(int worldX, int worldZ) {
return (noiseGenerator.noise(worldX, worldZ, 0.5D, 0.5D, true) + 1D) / 2D;
}
public int getBedrockHeight(int worldX, int worldZ) {
return (int) Math.min(minBedrockHeight, getNormalizedNoise(worldX, worldZ) * maxBedrockHeight);
}
}

View File

@ -2,13 +2,32 @@ package ca.recrown.islandsurvivalcraft.world.generation;
import org.bukkit.World; import org.bukkit.World;
import org.bukkit.block.Biome; import org.bukkit.block.Biome;
import org.bukkit.event.Listener;
import ca.recrown.islandsurvivalcraft.world.BiomeSelector; import ca.recrown.islandsurvivalcraft.world.BiomeSelector;
import ca.recrown.islandsurvivalcraft.world.IslandWorldMapper; import ca.recrown.islandsurvivalcraft.world.IslandWorldMapper;
public interface BiomeGenerator extends Listener { public interface BiomeGenerator {
public void initialize(World world, IslandWorldMapper mapGenerator, BiomeSelector biomeSelector);
public Biome GenerateBiome(int chunkX, int chunkZ, int localX, int localZ); /**
public BiomeGenerator getInstance(); * Given a biome array, requests for it to be saturated by the IslandWorldChunkGenerator.
* The array should store the columns of biomes for the entire chunk.
* It doesn't need to be populated on the first call as this method will be called once for every column in the chunk.
* However, if some biomes can be set without repetative calls, doing so will prevent this method from being called for those locals.
* @param biomesArray The array of biomes that are to be saturated.
* @param world The current world.
* @param chunkX The X coordinate of the chunk.
* @param chunkZ The Z coordinate of the chunk.
* @param localX The X coordinate of the column within the chunk.
* @param localZ The Z coordinate of the column within the chunk.
* @param mapper The island mapper to be used.
* @param tempGenerator The temperature generator to be used.
*/
public void generateBiomeColumn(Biome[][] biomesArray, World world, int chunkX, int chunkZ, int localX, int localZ, IslandWorldMapper mapper, BiomeSelector biomeSelector, TemperatureMapGenerator tempGenerator);
/**
* Called when Bukkit designates a new chunk as generated.
* @param chunkX The X coordinate of the chunk.
* @param chunkZ The Z coordinate of the chunk.
*/
public void chunkGenerated(int chunkX, int chunkZ);
} }

View File

@ -1,209 +0,0 @@
package ca.recrown.islandsurvivalcraft.world.generation;
import org.bukkit.Chunk;
import org.bukkit.World;
import org.bukkit.block.Biome;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.world.ChunkLoadEvent;
import ca.recrown.islandsurvivalcraft.Utilities;
import ca.recrown.islandsurvivalcraft.Types.Point2;
import ca.recrown.islandsurvivalcraft.caching.Cache;
import ca.recrown.islandsurvivalcraft.pathfinding.CoordinateTargetValidatable;
import ca.recrown.islandsurvivalcraft.pathfinding.CoordinateValidatable;
import ca.recrown.islandsurvivalcraft.pathfinding.DepthFirstSearch;
import ca.recrown.islandsurvivalcraft.world.BiomeSelector;
import ca.recrown.islandsurvivalcraft.world.IslandWorldMapper;
//Note: technically, the validators have to be run on land, and so, some condition checks may not be nessecary.
public class BiomePerIslandGenerator implements BiomeGenerator {
private volatile boolean initialized;
private final TemperatureMapGenerator temperatureMapGenerator;
private final Cache<Point2, Biome> chunkBiomesCache;
private final Cache<Point2, Boolean> chunkGenStatusCache;
private volatile IslandWorldMapper worldIslandMap;
private volatile BiomeSelector biomeSelector;
private volatile World world;
public BiomePerIslandGenerator() {
this.temperatureMapGenerator = new TemperatureMapGenerator();
chunkBiomesCache = new Cache<>(65536);
chunkGenStatusCache = new Cache<>(65536);
}
@Override
public void initialize(final World world, final IslandWorldMapper mapGenerator, final BiomeSelector biomeSelector) {
if (initialized) throw new IllegalStateException("Biome generator already initialized.");
initialized = true;
this.world = world;
this.worldIslandMap = mapGenerator;
this.biomeSelector = biomeSelector;
this.temperatureMapGenerator.setSeed(world.getSeed());
}
@EventHandler(priority = EventPriority.MONITOR)
public void onChunkLoaded(ChunkLoadEvent event) {
if (event.isNewChunk()) {
Chunk chunk = event.getChunk();
if (chunk.getWorld().getUID().hashCode() == world.getUID().hashCode() && chunk.getWorld().getUID().equals(world.getUID())) {
chunkGenStatusCache.setValue(new Point2(chunk.getX(), chunk.getZ()), true);
}
}
}
@Override
public BiomeGenerator getInstance() {
return new BiomePerIslandGenerator();
}
@Override
public Biome GenerateBiome(int chunkX, int chunkZ, int localX, int localZ) {
Point2 currChunkCoords = new Point2(chunkX, chunkZ);
int worldX = 16 * chunkX + localX;
int worldZ = 16 * chunkZ + localZ;
Point2 worldCoords = new Point2(worldX, worldZ);
Biome cachedBiome = getStoredBiome(worldCoords);
if (cachedBiome != null) return cachedBiome;
float temperature = temperatureMapGenerator.getTemperature(worldX, worldZ);
if (!worldIslandMap.isIsland(worldX, worldZ)) {
return biomeSelector.getOceanBiome(temperature);
}
IslandBiomeInfo biomeInfo = new IslandBiomeInfo();
DepthFirstSearch prevBiomeFinder = new DepthFirstSearch(biomeInfo);
prevBiomeFinder.setStartPosition(worldX, worldZ);
if (!prevBiomeFinder.findTarget(biomeInfo)) {
if (biomeInfo.main == null) biomeInfo.main = biomeSelector.getLandBiome(temperature);
if (biomeInfo.shore == null) biomeInfo.shore = biomeSelector.getShoreBiome(biomeInfo.main, temperature);
if (biomeInfo.shallow == null) biomeInfo.shallow = biomeSelector.getOceanBiome(temperature);
}
PropagatorInfo propInfo = new PropagatorInfo(biomeInfo.main, biomeInfo.shore, biomeInfo.shallow, currChunkCoords);
DepthFirstSearch prop = new DepthFirstSearch(propInfo);
prop.setStartPosition(worldX, worldZ);
prop.findTarget(propInfo);
if (worldIslandMap.isLand(worldX, worldZ)) {
if (worldIslandMap.isShore(worldX, worldZ)) {
return biomeInfo.shore;
} else {
return biomeInfo.main;
}
} else {
return biomeInfo.shallow;
}
}
private Biome getStoredBiome(Point2 worldCoords) {
Point2 chunkCoords = Utilities.worldToChunkCoordinates(worldCoords);
//Search our cache first.
Biome biome = chunkBiomesCache.getValue(worldCoords);
if (biome != null) return biome;
//Check to see if bukkit has anything to say about it.
Boolean chunkGenStat = chunkGenStatusCache.getValue(chunkCoords);
if (chunkGenStat == null) {
chunkGenStat = world.isChunkGenerated(chunkCoords.x, chunkCoords.y);
chunkGenStatusCache.setValue(chunkCoords, chunkGenStat);
}
if (chunkGenStat) {
biome = world.getBiome(worldCoords.x, 0, worldCoords.y);
chunkBiomesCache.setValue(worldCoords, biome);
return biome;
}
//Guess it's not cached or created.
return null;
}
private void setCacheBiome(Point2 worldCoords, Biome biome) {
//Set it in cache.
chunkBiomesCache.setValue(worldCoords, biome);
}
private class PropagatorInfo implements CoordinateTargetValidatable, CoordinateValidatable {
public final Biome main, shore, shallow;
private final Point2 currentChunkCoords;
private boolean hasSet;
@Override
public boolean isCoordinateTarget(int x, int y) {
hasSet = false;
Point2 worldCoords = new Point2(x, y);
if (worldIslandMap.isLand(x, y)) {
if (worldIslandMap.isShore(x, y)) {
setCacheBiome(worldCoords, shore);
hasSet = true;
} else {
setCacheBiome(worldCoords, main);
hasSet = true;
}
} else {
setCacheBiome(worldCoords, shallow);
hasSet = true;
}
if (!hasSet) throw new IllegalStateException();
return false;
}
@Override
public boolean validate(int x, int y) {
Point2 chunkCoords = Utilities.worldToChunkCoordinates(x, y);
return chunkCoords.fastEquals(currentChunkCoords) && worldIslandMap.isIsland(x, y);
}
public PropagatorInfo(Biome main, Biome shore, Biome shallow, Point2 currentChunkCoords) {
this.main = main;
this.shore = shore;
this.shallow = shallow;
this.currentChunkCoords = currentChunkCoords;
}
}
private class IslandBiomeInfo implements CoordinateTargetValidatable, CoordinateValidatable {
private boolean isLand, isShore, isShallow;
public Biome main, shore, shallow;
@Override
public boolean validate(int x, int y) {
isShore = false;
isShallow = false;
isLand = false;
return worldIslandMap.isIsland(x, y);
}
@Override
public boolean isCoordinateTarget(int x, int y) {
Point2 worldCoords = new Point2(x, y);
if ((isLand = worldIslandMap.isLand(x, y))) {
isShore = worldIslandMap.isShore(x, y);
} else {
isShallow = worldIslandMap.isShallowPortion(x, y);
}
if (main == null && isLand && !isShore) {
main = getStoredBiome(worldCoords);
} else
if (shore == null && isShore) {
shore = getStoredBiome(worldCoords);
} else
if (shallow == null && isShallow) {
shallow = getStoredBiome(worldCoords);
}
return allBiomesAcquired();
}
public boolean allBiomesAcquired() {
return shore != null && main != null && shallow != null;
}
}
}

View File

@ -0,0 +1,79 @@
package ca.recrown.islandsurvivalcraft.world.generation;
import java.util.Random;
import org.bukkit.Chunk;
import org.bukkit.Material;
import org.bukkit.World;
import org.bukkit.block.Biome;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.world.ChunkLoadEvent;
import org.bukkit.generator.ChunkGenerator;
import ca.recrown.islandsurvivalcraft.Utilities;
import ca.recrown.islandsurvivalcraft.world.BiomeSelector;
import ca.recrown.islandsurvivalcraft.world.IslandWorldMapper;
import ca.recrown.islandsurvivalcraft.world.shaders.WorldHeightShader;
public class IslandWorldChunkGenerator extends ChunkGenerator implements Listener {
private final BiomeGenerator biomeGenerator;
private final TemperatureMapGenerator temperatureGenerator = new TemperatureMapGenerator();
private volatile Random random;
private volatile IslandWorldMapper mapper;
private volatile BiomeSelector biomeSelector;
private volatile World world;
private volatile WorldHeightShader heightShader;
public IslandWorldChunkGenerator(BiomeGenerator biomeGenerator) {
this.biomeGenerator = biomeGenerator;
}
public IslandWorldChunkGenerator() {
this.biomeGenerator = new UniBiomeIslandGenerator();
}
@Override
public ChunkData generateChunkData(World world, Random random, int x, int z, BiomeGrid biome) {
if (this.random != random) {
this.random = random;
mapper = new IslandWorldMapper(random);
temperatureGenerator.setSeed(world.getSeed());
biomeSelector = new BiomeSelector(random);
biomeSelector.initialize();
this.world = world;
this.heightShader = new WorldHeightShader(world.getSeed(), mapper, world.getSeaLevel(), world.getMaxHeight(), 3);
}
int maxHeight = world.getMaxHeight();
ChunkData chunkData = createChunkData(world);
Biome[][] biomes = new Biome[Utilities.CHUNK_SIZE][Utilities.CHUNK_SIZE];
for (int localX = 0; localX < Utilities.CHUNK_SIZE; localX++) {
for (int localZ = 0; localZ < Utilities.CHUNK_SIZE; localZ++) {
if (biomes[localX][localZ] == null) {
biomeGenerator.generateBiomeColumn(biomes, world, x, z, localX, localZ, mapper, biomeSelector, temperatureGenerator);
}
if (biomes[localX][localZ] == null) throw new IllegalStateException("Biome was null.");
for (int y = 0; y < maxHeight; y++) {
biome.setBiome(x, y, z, biomes[localX][localZ]);
}
int worldX = 16 * x + localX;
int worldZ = 16 * z + localZ;
int height = heightShader.getAltitude(worldX, worldZ, biomes[localX][localZ]);
chunkData.setRegion(localX, 1, localZ, localX+1, height, localZ+1, Material.DIAMOND_BLOCK);
}
}
chunkData.setRegion(0, 0, 0, 16, 1, 16, Material.BEDROCK);
return super.generateChunkData(world, random, x, z, biome);
}
@EventHandler
public void onChunkLoaded(ChunkLoadEvent event) {
if (event.isNewChunk()) {
if (world == event.getWorld()) {
Chunk chunk = event.getChunk();
biomeGenerator.chunkGenerated(chunk.getX(), chunk.getZ());
}
}
}
}

View File

@ -4,7 +4,7 @@ import java.util.Random;
import org.bukkit.util.noise.SimplexOctaveGenerator; import org.bukkit.util.noise.SimplexOctaveGenerator;
import ca.recrown.islandsurvivalcraft.Types.Point2; import ca.recrown.islandsurvivalcraft.types.Point2;
import ca.recrown.islandsurvivalcraft.caching.Cache; import ca.recrown.islandsurvivalcraft.caching.Cache;
class TemperatureMapGenerator { class TemperatureMapGenerator {

View File

@ -0,0 +1,154 @@
package ca.recrown.islandsurvivalcraft.world.generation;
import org.bukkit.World;
import org.bukkit.block.Biome;
import ca.recrown.islandsurvivalcraft.Utilities;
import ca.recrown.islandsurvivalcraft.types.Point2;
import ca.recrown.islandsurvivalcraft.caching.Cache;
import ca.recrown.islandsurvivalcraft.pathfinding.CoordinateTargetValidatable;
import ca.recrown.islandsurvivalcraft.pathfinding.CoordinateValidatable;
import ca.recrown.islandsurvivalcraft.pathfinding.DepthFirstSearch;
import ca.recrown.islandsurvivalcraft.world.BiomeSelector;
import ca.recrown.islandsurvivalcraft.world.IslandWorldMapper;
public class UniBiomeIslandGenerator implements BiomeGenerator {
private final Cache<Point2, Biome> biomeCache = new Cache<>(12800);
private final Cache<Point2, Boolean> existenceCache = new Cache<>(12800);
@Override
public void generateBiomeColumn(Biome[][] biomes, World world, int chunkX, int chunkZ, int localX, int localZ, IslandWorldMapper mapper, BiomeSelector biomeSelector, TemperatureMapGenerator tempGen) {
int worldX = 16 * chunkX + localX;
int worldZ = 16 * chunkZ + localZ;
Point2 chunkCoords = Utilities.worldToChunkCoordinates(new Point2(worldX, worldZ));
existenceCache.setValue(chunkCoords, false);
//Check if we can just give it something in cache.
Biome biome = getSavedBiome(world, worldX, worldZ);
if (biome != null) {
biomes[localX][localZ] = biome;
return;
}
//Fine, check if it's ocean.
if (!mapper.isIsland(worldX, worldZ)) {
setCacheBiome(worldX, worldZ, biomeSelector.getOceanBiome(tempGen.getTemperature(worldX, worldZ)), biomes);
return;
}
//Shoot, looks like it's actually an island.
DepthFirstSearch search = new DepthFirstSearch();
search.setValidatable(mapper);
IslandInfo islandInfo = new IslandInfo(mapper, world);
search.setStartPosition(worldX, worldZ);
if (!search.findTarget(islandInfo)) {
float temp = tempGen.getTemperature(worldX, worldZ);
if (islandInfo.main == null) islandInfo.main = biomeSelector.getLandBiome(temp);
if (islandInfo.shore == null) islandInfo.shore = biomeSelector.getShoreBiome(islandInfo.main, temp);
if (islandInfo.shallow == null) islandInfo.shallow = biomeSelector.getOceanBiome(temp);
}
PropagatorInfo propInfo = new PropagatorInfo(islandInfo, biomes, new Point2(chunkX, chunkZ), mapper);
search.setValidatable(propInfo);
search.setStartPosition(worldX, worldZ);
search.findTarget(propInfo);
}
private Biome getSavedBiome(World world, int worldX, int worldZ) {
Point2 worldCoords = new Point2(worldX, worldZ);
Biome res = null;
res = biomeCache.getValue(worldCoords);
if (res != null) return res;
Point2 chunkCoords = Utilities.worldToChunkCoordinates(worldCoords);
Boolean chunkExists = existenceCache.getValue(chunkCoords);
if (chunkExists == null) {
chunkExists = world.isChunkGenerated(worldX, worldZ);
existenceCache.setValue(chunkCoords, chunkExists);
}
if (chunkExists) {
res = world.getBiome(worldX, 0, worldZ);
biomeCache.setValue(worldCoords, res);
}
return res;
}
public void chunkGenerated(int chunkX, int chunkZ) {
existenceCache.setValue(new Point2(chunkX, chunkZ), true);
}
private void setCacheBiome(int worldX, int worldZ, Biome biome, Biome[][] biomes) {
Point2 worldCoords = new Point2(worldX, worldZ);
if (biomes != null) {
Point2 localCoords = Utilities.worldToLocalChunkCoordinates(worldCoords);
biomes[localCoords.x][localCoords.y] = biome;
}
biomeCache.setValue(worldCoords, biome);
}
private class IslandInfo implements CoordinateTargetValidatable {
public final IslandWorldMapper mapper;
public final World world;
public Biome main, shore, shallow;
public IslandInfo(IslandWorldMapper mapper, World world) {
this.mapper = mapper;
this.world = world;
}
@Override
public boolean isCoordinateTarget(int x, int y) {
if (mapper.isLand(x, y)) {
if (mapper.isShore(x, y)) {
shore = getSavedBiome(world, x, y);
} else {
main = getSavedBiome(world, x, y);
}
} else {
shallow = getSavedBiome(world, x, y);
}
return main != null && shore != null && shallow != null;
}
}
private class PropagatorInfo implements CoordinateTargetValidatable, CoordinateValidatable {
private final Biome shallow, shore, main;
private final Biome[][] biomes;
private final Point2 chunkCoords;
private final IslandWorldMapper mapper;
public PropagatorInfo(IslandInfo islandInfo, Biome[][] biomes, Point2 chunkCoords, IslandWorldMapper mapper) {
this.shallow = islandInfo.shallow;
this.shore = islandInfo.shore;
this.main = islandInfo.main;
this.biomes = biomes;
this.chunkCoords = chunkCoords;
this.mapper = mapper;
}
@Override
public boolean validate(int x, int y) {
Point2 chunkCoords = Utilities.worldToChunkCoordinates(new Point2(x, y));
return this.chunkCoords.fastEquals(chunkCoords) && mapper.validate(x, y);
}
@Override
public boolean isCoordinateTarget(int x, int y) {
if (mapper.isLand(x, y)) {
if (mapper.isShore(x, y)) {
setCacheBiome(x, y, shore, biomes);
} else {
setCacheBiome(x, y, main, biomes);
}
} else {
setCacheBiome(x, y, shallow, biomes);
}
return false;
}
}
}

View File

@ -1,80 +0,0 @@
package ca.recrown.islandsurvivalcraft.world.generation;
import java.security.InvalidAlgorithmParameterException;
import java.util.Random;
import org.bukkit.Material;
import org.bukkit.World;
import org.bukkit.block.Biome;
import org.bukkit.generator.ChunkGenerator.BiomeGrid;
import org.bukkit.generator.ChunkGenerator.ChunkData;
import org.bukkit.plugin.java.JavaPlugin;
import ca.recrown.islandsurvivalcraft.world.BiomeSelector;
import ca.recrown.islandsurvivalcraft.world.IslandWorldMapper;
import ca.recrown.islandsurvivalcraft.world.shaders.WorldHeightShader;
/**
* A world generator.
*/
public class WorldGenerator {
private final int maxHeight;
private final BedrockGenerator bedrockGenerator;
private final BiomeSelector biomeSelector;
private final BiomeGenerator biomeGenerator;
private final WorldHeightShader heightShader;
public final World world;
public final IslandWorldMapper islandMapGenerator;
public final Random random;
public WorldGenerator(JavaPlugin plugin, World world, BiomeGenerator islandBiomeGenerator, Random random) {
this.world = world;
this.maxHeight = world.getMaxHeight();
this.random = random;
this.biomeSelector = new BiomeSelector(random);
this.islandMapGenerator = new IslandWorldMapper(random);
this.biomeGenerator = islandBiomeGenerator;
int bedrockMaxHeight = 5;
int bedrockMinHeight = 1;
this.bedrockGenerator = new BedrockGenerator(random, bedrockMaxHeight, bedrockMinHeight);
this.heightShader = new WorldHeightShader(world.getSeed(), islandMapGenerator, world.getSeaLevel(), maxHeight,
bedrockMaxHeight + 2);
biomeSelector.initialize();
islandBiomeGenerator.initialize(world, islandMapGenerator, biomeSelector);
plugin.getServer().getPluginManager().registerEvents(islandBiomeGenerator, plugin);
}
public void GenerateChunk(int chunkX, int chunkZ, int localX, int localZ, ChunkData chunk, BiomeGrid biomeGrid) {
int worldX = 16 * chunkX + localX;
int worldZ = 16 * chunkZ + localZ;
//Sets the biome.
Biome currentBiome = biomeGenerator.GenerateBiome(chunkX, chunkZ, localX, localZ);
if (currentBiome == null) throw new IllegalStateException("Biome generated was null!");
for (int y = 0; y < maxHeight; y++) {
biomeGrid.setBiome(localX, y, localZ, currentBiome);
}
//get height shader.
int height = 0;
try {
height = heightShader.getAltitude(worldX, worldZ, currentBiome);
} catch (InvalidAlgorithmParameterException e) {
e.printStackTrace();
height = maxHeight;
}
if (height == 0) throw new IllegalStateException("Height generated was null!");
// gets the bedrock height.
int bedrockHeight = bedrockGenerator.getBedrockHeight(worldX, worldZ);
//set general shape
chunk.setRegion(localX, bedrockHeight, localZ, localX + 1, height, localZ + 1, Material.STONE);
//set bedrock last
chunk.setRegion(localX, 0, localZ, localX + 1, bedrockHeight, localZ + 1, Material.BEDROCK);
}
}

View File

@ -1,32 +0,0 @@
package ca.recrown.islandsurvivalcraft.world.generation.alternation;
import java.util.Random;
import org.bukkit.World;
import org.bukkit.generator.ChunkGenerator;
import org.bukkit.plugin.java.JavaPlugin;
import ca.recrown.islandsurvivalcraft.world.generation.BiomeGenerator;
import ca.recrown.islandsurvivalcraft.world.generation.WorldGenerator;
public class AlternatingChunkGenerator extends ChunkGenerator {
private final WorldGeneratorAlternator alternator;
public AlternatingChunkGenerator(JavaPlugin plugin, BiomeGenerator biomeGenerator) {
alternator = new WorldGeneratorAlternator(plugin, biomeGenerator);
}
@Override
public ChunkData generateChunkData(World world, Random random, int chunkX, int chunkZ, BiomeGrid biome) {
WorldGenerator worldGenerator = alternator.getGenerator(world, random);
ChunkData chunk = createChunkData(world);
for (int localX = 0; localX < 16; localX++) {
for (int localZ = 0; localZ < 16; localZ++) {
worldGenerator.GenerateChunk(chunkX, chunkZ, localX, localZ, chunk, biome);
}
}
return chunk;
}
}

View File

@ -1,58 +0,0 @@
package ca.recrown.islandsurvivalcraft.world.generation.alternation;
import java.util.HashMap;
import java.util.Random;
import java.util.UUID;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock.ReadLock;
import java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock;
import org.bukkit.World;
import org.bukkit.plugin.java.JavaPlugin;
import ca.recrown.islandsurvivalcraft.world.generation.BiomeGenerator;
import ca.recrown.islandsurvivalcraft.world.generation.WorldGenerator;
/**
* Alternates the data used on a per world basis.
* Uses IslandWorldGenerator as the container for each world.
*/
public class WorldGeneratorAlternator {
private final HashMap<UUID, WorldGenerator> chunkGenerators;
private volatile WorldGenerator lastGenerator;
private volatile UUID lastUUID;
private final BiomeGenerator islandBiomeGenerator;
private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock(true);
private final ReadLock readLock = lock.readLock();
private final WriteLock writeLock = lock.writeLock();
private final JavaPlugin plugin;
public WorldGeneratorAlternator(JavaPlugin plugin, BiomeGenerator biomeGenerator) {
this.chunkGenerators = new HashMap<>();
this.islandBiomeGenerator = biomeGenerator;
this.plugin = plugin;
}
public WorldGenerator getGenerator(World world, Random random) {
if (lastUUID == null || (lastUUID.hashCode() != world.getUID().hashCode() && lastUUID.equals(world.getUID()))) {
System.out.println("Alternating generator for world: " + world.getName());
lastUUID = world.getUID();
readLock.lock();
try {
lastGenerator = chunkGenerators.get(lastUUID);
} finally {
readLock.unlock();
}
if (lastGenerator == null) {
lastGenerator = new WorldGenerator(plugin, world, islandBiomeGenerator, random);
writeLock.lock();
try {
chunkGenerators.put(lastUUID, lastGenerator);
} finally {
writeLock.unlock();
}
}
}
return lastGenerator;
}
}

View File

@ -1,6 +1,5 @@
package ca.recrown.islandsurvivalcraft.world.shaders; package ca.recrown.islandsurvivalcraft.world.shaders;
import java.security.InvalidAlgorithmParameterException;
import java.util.Random; import java.util.Random;
import org.bukkit.block.Biome; import org.bukkit.block.Biome;
@ -27,7 +26,7 @@ public class WorldHeightShader {
this.minimumHeight = minimumHeight; this.minimumHeight = minimumHeight;
} }
public int getAltitude(int worldX, int worldZ, Biome biome) throws InvalidAlgorithmParameterException { public int getAltitude(int worldX, int worldZ, Biome biome) {
double modifier = getHeightModifier(worldX, worldZ); double modifier = getHeightModifier(worldX, worldZ);
int baseValue = calculateTerrainHeight(worldX, worldZ); int baseValue = calculateTerrainHeight(worldX, worldZ);
int height = 0; int height = 0;
@ -50,13 +49,13 @@ public class WorldHeightShader {
height = (int) (getNormalizedHeightModifier(worldX, worldZ) * 5D + baseValue); height = (int) (getNormalizedHeightModifier(worldX, worldZ) * 5D + baseValue);
} }
if (height > worldHeight) throw new InvalidAlgorithmParameterException("Resulting height is greater than world height! Biome this occurred on: " + biomeName); if (height > worldHeight) throw new IllegalStateException("Resulting height is greater than world height! Biome this occurred on: " + biomeName);
return height; return height;
} }
private int calculateTerrainHeight(int worldX, int worldZ) { private int calculateTerrainHeight(int worldX, int worldZ) {
double islandValue = islandLocator.getWorldBlockValue(worldX, worldZ) + 1D; double islandValue = islandLocator.getWorldBlockValue(worldX, worldZ) + 1D;
islandValue *= worldHeight/2; islandValue *= worldHeight/4;
return (int) Math.max(Math.min(seaLevel, islandValue), minimumHeight); return (int) Math.max(Math.min(seaLevel, islandValue), minimumHeight);
} }

View File

@ -9,7 +9,7 @@ import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestInstance; import org.junit.jupiter.api.TestInstance;
import org.junit.jupiter.api.TestInstance.Lifecycle; import org.junit.jupiter.api.TestInstance.Lifecycle;
import ca.recrown.islandsurvivalcraft.Types.Point2; import ca.recrown.islandsurvivalcraft.types.Point2;
/** /**
* Unit test for simple App. * Unit test for simple App.
@ -85,8 +85,8 @@ public class UtilitiesTest {
@Test @Test
public void testWorldToLocalCoordinatesPositive() { public void testWorldToLocalCoordinatesPositive() {
assertEquals(new Point2(6, 2), Utilities.worldToLocalChunkCoordinates(39, 83)); assertEquals(new Point2(7, 3), Utilities.worldToLocalChunkCoordinates(39, 83));
assertEquals(new Point2(6, 2), Utilities.worldToLocalChunkCoordinates(new Point2(39, 83))); assertEquals(new Point2(7, 3), Utilities.worldToLocalChunkCoordinates(new Point2(39, 83)));
} }
@Test @Test
@ -94,4 +94,31 @@ public class UtilitiesTest {
assertEquals(new Point2(0, 0), Utilities.worldToLocalChunkCoordinates(1024, 32)); assertEquals(new Point2(0, 0), Utilities.worldToLocalChunkCoordinates(1024, 32));
assertEquals(new Point2(0, 0), Utilities.worldToLocalChunkCoordinates(new Point2(16, 80))); assertEquals(new Point2(0, 0), Utilities.worldToLocalChunkCoordinates(new Point2(16, 80)));
} }
@Test
public void testWorldToLocalCoordinatesPositiveEntirety() {
int chunkX = 4;
int chunkZ = 3;
for (int x = 0; x < Utilities.CHUNK_SIZE; x++) {
for (int z = 0; z < Utilities.CHUNK_SIZE; z++) {
int worldX = Utilities.CHUNK_SIZE * chunkX + x;
int worldZ = Utilities.CHUNK_SIZE * chunkZ + z;
assertEquals(new Point2(x, z), Utilities.worldToLocalChunkCoordinates(new Point2(worldX, worldZ)));
} }
}
}
@Test
public void testWorldToLocalCoordinatesNegativeEntirety() {
int chunkX = -42;
int chunkZ = -3;
for (int x = 0; x < Utilities.CHUNK_SIZE; x++) {
for (int z = 0; z < Utilities.CHUNK_SIZE; z++) {
int worldX = Utilities.CHUNK_SIZE * chunkX + x;
int worldZ = Utilities.CHUNK_SIZE * chunkZ + z;
assertEquals(new Point2(x, z), Utilities.worldToLocalChunkCoordinates(new Point2(worldX, worldZ)));
}
}
}
}