Merge pull request #106 from NCrashed/MC1.7

Fixes #93. Asteroid generation redesign based on combination of old and new systems.
This commit is contained in:
LemADEC 2015-10-30 10:14:45 +01:00
commit 8d2fcdca67
3 changed files with 153 additions and 219 deletions

View file

@ -1,7 +1,6 @@
package cr0s.warpdrive.config.structures;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import org.w3c.dom.Element;
@ -11,17 +10,18 @@ import cr0s.warpdrive.config.InvalidXmlException;
import cr0s.warpdrive.config.MetaBlock;
import net.minecraft.block.Block;
import net.minecraft.init.Blocks;
import net.minecraft.util.MathHelper;
import net.minecraft.world.World;
public class Asteroid extends Orb {
private static final int MIN_RADIUS = 5;
private static final int MIN_RADIUS = 1;
private static final int CORE_MAX_TRIES = 10;
private Block coreBlock;
private int maxCoreSize, minCoreSize;
private double coreRad;
public Asteroid() {
super(0); //Diameter not relevant
}
@ -47,177 +47,71 @@ public class Asteroid extends Orb {
} catch (NumberFormatException gdbg) {
throw new InvalidXmlException("Asteroid core size dimensions are NaN!");
}
try {
String coreRadStr = e.getAttribute("coreRad");
if(coreRadStr.isEmpty()) {
coreRad = 0.1;
} else {
coreRad = Double.parseDouble(e.getAttribute("coreRad"));
}
} catch (NumberFormatException gdbg) {
throw new InvalidXmlException("Asteroid core rad must be double!");
}
}
@Override
public boolean generate(World world, Random rand, int x, int y, int z) {
int randRadius = MIN_RADIUS + rand.nextInt(Math.max(1, getRadius() - MIN_RADIUS));
int numberCoreBlocks = minCoreSize + rand.nextInt(maxCoreSize - minCoreSize);
int randRadius = MIN_RADIUS + rand.nextInt(getRadius() - MIN_RADIUS);
WarpDrive.logger.info("Asteroid generation: radius=" + randRadius + ", numCoreBlocks=" + numberCoreBlocks);
//Max theoretical range is a core in a straight line, plus
ExclusiveLocationFactory locFact = new ExclusiveLocationFactory(randRadius + numberCoreBlocks + 1, x, y, z);
WarpDrive.logger.info("Asteroid generation: radius=" + randRadius + ", numCoreBlocks=" + numberCoreBlocks + ", coreRad=" + coreRad);
//Use this to generate a abstract form for the core.
ArrayList<Location> coreLocations = generateCore(world, rand, x, y, z, numberCoreBlocks, coreBlock, numberCoreBlocks, locFact);
//Get the initial blocks placed (custom generateCore now just returns them)
//ArrayList<Location> coreLocations = getAdjacentIdenticalBlocks(world, coreBlock, x, y, z, locFact, true);
for (int currRad = 1; currRad < randRadius; currRad++) {
WarpDrive.logger.info("Generating asteroid layer " + currRad + ":" + coreLocations.size());
//Build a layer over the existing
coreLocations = addLayer(world, rand, coreLocations, this.getShellForRadius(currRad), locFact);
ArrayList<Location> coreLocations = generateCore(world, rand, x, y, z, numberCoreBlocks, coreBlock, numberCoreBlocks, randRadius);
for (Location coreLocation: coreLocations) {
// Calculate mininum distance to borders of generation area
int maxRadX = Math.min(x+randRadius-coreLocation.x, coreLocation.x - (x - randRadius));
int maxRadY = Math.min(y+randRadius-coreLocation.y, coreLocation.y - (y - randRadius));
int maxRadZ = Math.min(z+randRadius-coreLocation.z, coreLocation.z - (z - randRadius));
int maxLocalRadius = Math.min(maxRadX, Math.min(maxRadY, maxRadZ));
// Generate shell
addShell(world, rand, coreLocation, maxLocalRadius);
}
return true;
}
/**
* Get a list of all the blocks that are adjacent to this block, i.e. are within one block radius of the the the passed location.
*
* If recurse is passed as true, the returned list will also include the results from calling this method on all the blocks that it finds. Thus, it also returns blocks adjacent to the blocks that
* are adjacent to the blocks that are adjacent.... you get my meaning.
*
* Since it uses an ExclusiveLocationFactory, the returned list will be a list of unique locations in no particular order.
*
* @param world
* World to check the blocks in
* @param coreBlock
* The block type that should be looked for
* @param xCenter
* The x coordinate to start at
* @param yCenter
* The y coordinate to start at
* @param zCenter
* The z coordinate to start at
* @param locFact
* An ExcelusiveLocationFactory to get locations from.
* @param recurse
* Whether to include the output of this method called with each block that the method originally finds.
* @return
* Creates a shell sphere around given core location.
*
* @param world World to place shell
* @param rand Random generator
* @param l Location of core block
* @param maxRad Maximum radius of asteroid
*/
private ArrayList<Location> getAdjacentIdenticalBlocks(World world, Block coreBlock, int xCenter, int yCenter, int zCenter, ExclusiveLocationFactory locFact, boolean recurse) {
ArrayList<Location> foundBlocks = new ArrayList<Location>();
//There are a total of 26 blocks around the center one
for (int x = -1; x <= 1; x++) {
for (int y = -1; y <= 1; y++) {
for (int z = -1; z <= 1; z++) {
//Check if the block is of interest
if (coreBlock.isAssociatedBlock(world.getBlock(xCenter + x, yCenter + y, zCenter + z))) {
//Get a location for the block.
//If the location comes back null, it has already been added, and we dont care about it.
Location curr = locFact.getLocation(xCenter + x, yCenter + y, zCenter + z);
if (curr != null) {
foundBlocks.add(curr);
//Check whether we should recurse through the blocks,
// and if so, add all the blocks it finds to the list to return.
//Make sure not to recurse to the center block, but we are ok with adding it if it is actually the correct block.
if (recurse && (x != 0 || y != 0 || z != 0))
foundBlocks.addAll(getAdjacentIdenticalBlocks(world, coreBlock, xCenter + x, yCenter + y, zCenter + z, locFact, true));
}
private void addShell(World world, Random rand, Location l, int maxRad) {
//int rad = MIN_RADIUS + rand.nextInt(Math.max(1, maxRad - MIN_RADIUS));
int rad = maxRad;
// Iterate all blocks withing cube with side 2*rad
for(int x = l.x - rad; x <= l.x + rad; ++x) {
for(int y = l.y - rad; y <= l.y + rad; ++y) {
for(int z = l.z - rad; z <= l.z + rad; ++z) {
// current radius
int r = (int)Math.round(Math.sqrt((l.x - x)*(l.x - x) + (l.y - y)*(l.y - y) + (l.z - z)*(l.z - z)));
// if inside radius
if(r <= rad && isBlockEmpty(world, x, y, z)) {
OrbShell shell = getShellForRadius(r);
MetaBlock blType = shell.getRandomBlock(rand);
world.setBlock(x, y, z, blType.block, blType.metadata, 0);
}
}
}
}
return foundBlocks;
}
private ArrayList<Location> addLayer(World world, Random rand, List<Location> blocks, OrbShell orbShell, ExclusiveLocationFactory locFact) {
ArrayList<Location> addedBlocks = new ArrayList<Location>();
for (Location baseBlock : blocks) {
//for each block, get the air blocks adjacent to it, and turn them to a random block from the orbshell.
for (Location blToChange : getAdjacentIdenticalBlocks(world, Blocks.air, baseBlock.x, baseBlock.y, baseBlock.z, locFact, false)) {
//Set block without notify
MetaBlock blType = orbShell.getRandomBlock(rand);
world.setBlock(blToChange.x, blToChange.y, blToChange.z, blType.block, blType.metadata, 0);
addedBlocks.add(blToChange);
}
}
return addedBlocks;
}
/**
* ExclusiveLocationFactory is used so that any one location can only ever be retrieved once.
*
*/
private class ExclusiveLocationFactory {
//If a location is true, it has already been retrieved
private boolean[][][] existingLocs;
private int centerX, centerY, centerZ;
/**
* Creates a new ExclusiveLocationFactory.
*
* @param maxRange
* The maximum distance a request could be for
* @param centerX
* The center X coordinate
* @param centerY
* The center Y coordinate
* @param centerZ
* The center Z coordinate
*/
public ExclusiveLocationFactory(int maxRange, int centerX, int centerY, int centerZ) {
existingLocs = new boolean[maxRange][maxRange][maxRange];
int centerPoint = maxRange / 2;
this.centerX = centerX + centerPoint;
this.centerY = centerY + centerPoint;
this.centerZ = centerZ + centerPoint;
}
/**
* Gets a location at the coordinates, or null if it has already been retrieved.
*
* @param x
* @param y
* @param z
* @return
*/
public Location getLocation(int x, int y, int z){
//Get whether the location has already been accessed.
//Direction doesn't matter, only that there's a difference
if (!existingLocs[centerX - x][centerY - y][centerZ - z]) {
existingLocs[centerX - x][centerY - y][centerZ - z] = true;
return new Location(x, y, z);
}
return null;
}
}
/**
@ -237,68 +131,64 @@ public class Asteroid extends Orb {
}
/**
* Adapted from WorldGenMinable
*
* Checks if given coordinate empty (air in terms of MC).
* @param world
* @param rand
* @param x
* @param y
* @param z
* @param numberOfBlocks
* @param block
* @param metadata
* @param locFact
* @return
*/
private static boolean isBlockEmpty(World world, int x, int y, int z) {
return world.getBlock(x, y, z).isReplaceableOreGen(world, x, y, z, Blocks.air);
}
/**
* Generates core with simplified algorithm that tries to place numberOfBlocks cores within a core sphere (specified in config by "coreRad" value from 0.0 to 1.0).
*
* Note: CORE_MAX_TRIES defines how many tries the method applies before giving up placing a core.
*
* @param world - World where to place cores
* @param rand - Random generator
* @param x - center X coord
* @param y - center Y coord
* @param z - center Z coord
* @param numberOfBlocks - number of core blocks to place
* @param block - type of block to place
* @param metadata - metadata of bloeck to place
* @param maxRange - max radius of asteroid
* @return List of placed locations of cores
*/
private ArrayList<Location> generateCore(World world, Random rand, int x, int y, int z, int numberOfBlocks, Block block, int metadata,
ExclusiveLocationFactory locFact) {
int maxRange) {
ArrayList<Location> addedBlocks = new ArrayList<Location>();
float f = rand.nextFloat() * (float) Math.PI;
double d0 = x + 8 + MathHelper.sin(f) * numberOfBlocks / 8.0F;
double d1 = x + 8 - MathHelper.sin(f) * numberOfBlocks / 8.0F;
double d2 = z + 8 + MathHelper.cos(f) * numberOfBlocks / 8.0F;
double d3 = z + 8 - MathHelper.cos(f) * numberOfBlocks / 8.0F;
double d4 = y + rand.nextInt(3) - 2;
double d5 = y + rand.nextInt(3) - 2;
for (int l = 0; l <= numberOfBlocks; ++l) {
double d6 = d0 + (d1 - d0) * l / numberOfBlocks;
double d7 = d4 + (d5 - d4) * l / numberOfBlocks;
double d8 = d2 + (d3 - d2) * l / numberOfBlocks;
double d9 = rand.nextDouble() * numberOfBlocks / 16.0D;
double d10 = (MathHelper.sin(l * (float) Math.PI / numberOfBlocks) + 1.0F) * d9 + 1.0D;
double d11 = (MathHelper.sin(l * (float) Math.PI / numberOfBlocks) + 1.0F) * d9 + 1.0D;
int i1 = MathHelper.floor_double(d6 - d10 / 2.0D);
int j1 = MathHelper.floor_double(d7 - d11 / 2.0D);
int k1 = MathHelper.floor_double(d8 - d10 / 2.0D);
int l1 = MathHelper.floor_double(d6 + d10 / 2.0D);
int i2 = MathHelper.floor_double(d7 + d11 / 2.0D);
int j2 = MathHelper.floor_double(d8 + d10 / 2.0D);
for (int k2 = i1; k2 <= l1; ++k2) {
double d12 = (k2 + 0.5D - d6) / (d10 / 2.0D);
if (d12 * d12 < 1.0D) {
for (int l2 = j1; l2 <= i2; ++l2) {
double d13 = (l2 + 0.5D - d7) / (d11 / 2.0D);
if (d12 * d12 + d13 * d13 < 1.0D) {
for (int i3 = k1; i3 <= j2; ++i3) {
double d14 = (i3 + 0.5D - d8) / (d10 / 2.0D);
if (d12 * d12 + d13 * d13 + d14 * d14 < 1.0D && world.getBlock(k2, l2, i3).isReplaceableOreGen(world, k2, l2, i3, Blocks.air)) {
world.setBlock(k2, l2, i3, block, metadata, 2);
addedBlocks.add(locFact.getLocation(k2, l2, i3));
}
}
}
}
int coreRange = (int)Math.round(coreRad * maxRange);
int maxX = x + coreRange;
int minX = x - coreRange;
int maxY = y + coreRange;
int minY = y - coreRange;
int maxZ = z + coreRange;
int minZ = z - coreRange;
for (int i = 0; i < numberOfBlocks; ++i) {
int curX = x;
int curY = y;
int curZ = z;
boolean stopWalk = false;
for(int step = 0; step <= CORE_MAX_TRIES && !stopWalk; ++step) {
curX = rand.nextInt(Math.max(1, maxX - minX)) + minX;
curY = rand.nextInt(Math.max(1, maxY - minY)) + minY;
curZ = rand.nextInt(Math.max(1, maxZ - minZ)) + minZ;
if (isBlockEmpty(world, curX, curY, curZ)) {
world.setBlock(curX, curY, curZ, block, metadata, 2);
addedBlocks.add(new Location(curX, curY, curZ));
stopWalk = true;
}
}
}
return addedBlocks;
}

View file

@ -4,7 +4,9 @@ import java.io.File;
import java.io.FilenameFilter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.NavigableMap;
import java.util.Random;
import java.util.TreeMap;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
@ -23,7 +25,9 @@ public class StructureManager {
private static ArrayList<Star> stars = new ArrayList<Star>();
private static ArrayList<Planetoid> moons = new ArrayList<Planetoid>();
private static ArrayList<Planetoid> gasClouds = new ArrayList<Planetoid>();
private static ArrayList<Asteroid> asteroids = new ArrayList<Asteroid>();
private static RandomCollection<Asteroid> randomAsteroids = new RandomCollection<Asteroid>();
public static void loadStructures(String structureConfDir) {
loadStructures(new File(structureConfDir));
@ -100,6 +104,20 @@ public class StructureManager {
Asteroid as = new Asteroid();
as.loadFromXmlElement(struct);
asteroids.add(as);
// Load weighted collection to query it later
try {
int weight = 1;
String weightStr = struct.getAttribute("weight");
if(!weightStr.isEmpty()) {
weight = Integer.parseInt(struct.getAttribute("weight"));
}
weight = Math.max(1, weight);
randomAsteroids.add(weight, as);
} catch (NumberFormatException gdbg) {
throw new InvalidXmlException("Asteroid weight must be int!");
}
}
}
}
@ -113,7 +131,7 @@ public class StructureManager {
} else if (type.equalsIgnoreCase("moon")) {
return moons.get(random.nextInt(moons.size()));
} else if (type.equalsIgnoreCase("asteroid")) {
return asteroids.get(random.nextInt(asteroids.size()));
return randomAsteroids.next(random);
}
} else {
for (Star star : stars) {
@ -141,4 +159,27 @@ public class StructureManager {
public static DeployableStructure getGasCloud(Random random, final String name) {
return getStructure(random, name, "cloud");
}
/**
* Collection of elements with weights. Helps to select element with controlled odds.
*
* @author ncrashed
*
* @param <E>
*/
private static class RandomCollection<E> {
private final NavigableMap<Double, E> map = new TreeMap<Double, E>();
private double total = 0;
public void add(double weight, E result) {
if (weight <= 0) return;
total += weight;
map.put(total, result);
}
public E next(Random random) {
double value = random.nextDouble() * total;
return map.ceilingEntry(value).getValue();
}
}
}

View file

@ -68,21 +68,24 @@
</shell>
</structure>
<structure group="asteroid" name="asteroid" coreBlock="minecraft:iron_ore" maxCoreSize="10" minCoreSize="6">
<shell name="mantle" minThickness="2" maxThickness="10" fillerSets="commonOres,uncommonOres">
<structure group="asteroid" name="ClusteredAsteroid" weight="3" coreBlock="minecraft:iron_ore" maxCoreSize="10" minCoreSize="6" coreRad="0.5">
<shell name="mantle" minThickness="5" maxThickness="15" fillerSets="commonOres,uncommonOres">
<filler weight="10" block="minecraft:stone" />
<filler weight="5" block="minecraft:cobblestone" />
</shell>
</structure>
<structure group="asteroid" name="diamondGeode" coreBlock="minecraft:diamond_ore" maxCoreSize="2" minCoreSize="1">
<shell name="mantle" minThickness="5" maxThickness="25" fillerSets="commonOres">
</structure>
<structure group="asteroid" name="SphericalAsteroid" weight="10" coreBlock="minecraft:iron_ore" maxCoreSize="3" minCoreSize="1" coreRad="0.1">
<shell name="mantle" minThickness="5" maxThickness="10" fillerSets="commonOres,uncommonOres">
<filler weight="10" block="minecraft:stone" />
<filler weight="5" block="minecraft:cobblestone" />
</shell>
</structure>
</structure>
<structure group="asteroid" name="diamondGeode" weight="2" coreBlock="minecraft:diamond_ore" maxCoreSize="2" minCoreSize="1" coreRad="0.1">
<shell name="mantle" minThickness="5" maxThickness="10" fillerSets="commonOres">
<filler weight="10" block="minecraft:stone" />
<filler weight="5" block="minecraft:cobblestone" />
</shell>
</structure>
</warpdriveWorldGeneration>