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; package cr0s.warpdrive.config.structures;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List;
import java.util.Random; import java.util.Random;
import org.w3c.dom.Element; import org.w3c.dom.Element;
@ -11,17 +10,18 @@ import cr0s.warpdrive.config.InvalidXmlException;
import cr0s.warpdrive.config.MetaBlock; import cr0s.warpdrive.config.MetaBlock;
import net.minecraft.block.Block; import net.minecraft.block.Block;
import net.minecraft.init.Blocks; import net.minecraft.init.Blocks;
import net.minecraft.util.MathHelper;
import net.minecraft.world.World; import net.minecraft.world.World;
public class Asteroid extends Orb { 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 Block coreBlock;
private int maxCoreSize, minCoreSize; private int maxCoreSize, minCoreSize;
private double coreRad;
public Asteroid() { public Asteroid() {
super(0); //Diameter not relevant super(0); //Diameter not relevant
} }
@ -47,177 +47,71 @@ public class Asteroid extends Orb {
} catch (NumberFormatException gdbg) { } catch (NumberFormatException gdbg) {
throw new InvalidXmlException("Asteroid core size dimensions are NaN!"); 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 @Override
public boolean generate(World world, Random rand, int x, int y, int z) { 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 numberCoreBlocks = minCoreSize + rand.nextInt(maxCoreSize - minCoreSize);
int randRadius = MIN_RADIUS + rand.nextInt(getRadius() - MIN_RADIUS);
WarpDrive.logger.info("Asteroid generation: radius=" + randRadius + ", numCoreBlocks=" + numberCoreBlocks + ", coreRad=" + coreRad);
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);
//Use this to generate a abstract form for the core. //Use this to generate a abstract form for the core.
ArrayList<Location> coreLocations = generateCore(world, rand, x, y, z, numberCoreBlocks, coreBlock, numberCoreBlocks, locFact); ArrayList<Location> coreLocations = generateCore(world, rand, x, y, z, numberCoreBlocks, coreBlock, numberCoreBlocks, randRadius);
//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);
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; 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. * Creates a shell sphere around given core 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 * @param world World to place shell
* are adjacent to the blocks that are adjacent.... you get my meaning. * @param rand Random generator
* * @param l Location of core block
* Since it uses an ExclusiveLocationFactory, the returned list will be a list of unique locations in no particular order. * @param maxRad Maximum radius of asteroid
*
* @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
*/ */
private ArrayList<Location> getAdjacentIdenticalBlocks(World world, Block coreBlock, int xCenter, int yCenter, int zCenter, ExclusiveLocationFactory locFact, boolean recurse) { private void addShell(World world, Random rand, Location l, int maxRad) {
//int rad = MIN_RADIUS + rand.nextInt(Math.max(1, maxRad - MIN_RADIUS));
ArrayList<Location> foundBlocks = new ArrayList<Location>(); int rad = maxRad;
//There are a total of 26 blocks around the center one // Iterate all blocks withing cube with side 2*rad
for (int x = -1; x <= 1; x++) { for(int x = l.x - rad; x <= l.x + rad; ++x) {
for(int y = l.y - rad; y <= l.y + rad; ++y) {
for (int y = -1; y <= 1; y++) { for(int z = l.z - rad; z <= l.z + rad; ++z) {
// current radius
for (int z = -1; z <= 1; z++) { 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
//Check if the block is of interest if(r <= rad && isBlockEmpty(world, x, y, z)) {
if (coreBlock.isAssociatedBlock(world.getBlock(xCenter + x, yCenter + y, zCenter + z))) { OrbShell shell = getShellForRadius(r);
MetaBlock blType = shell.getRandomBlock(rand);
//Get a location for the block. world.setBlock(x, y, z, blType.block, blType.metadata, 0);
//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));
}
} }
} }
} }
} }
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 world
* @param rand
* @param x * @param x
* @param y * @param y
* @param z * @param z
* @param numberOfBlocks
* @param block
* @param metadata
* @param locFact
* @return * @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, 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>(); ArrayList<Location> addedBlocks = new ArrayList<Location>();
int coreRange = (int)Math.round(coreRad * maxRange);
float f = rand.nextFloat() * (float) Math.PI; int maxX = x + coreRange;
double d0 = x + 8 + MathHelper.sin(f) * numberOfBlocks / 8.0F; int minX = x - coreRange;
double d1 = x + 8 - MathHelper.sin(f) * numberOfBlocks / 8.0F; int maxY = y + coreRange;
double d2 = z + 8 + MathHelper.cos(f) * numberOfBlocks / 8.0F; int minY = y - coreRange;
double d3 = z + 8 - MathHelper.cos(f) * numberOfBlocks / 8.0F; int maxZ = z + coreRange;
double d4 = y + rand.nextInt(3) - 2; int minZ = z - coreRange;
double d5 = y + rand.nextInt(3) - 2;
for (int i = 0; i < numberOfBlocks; ++i) {
for (int l = 0; l <= numberOfBlocks; ++l) { int curX = x;
double d6 = d0 + (d1 - d0) * l / numberOfBlocks; int curY = y;
double d7 = d4 + (d5 - d4) * l / numberOfBlocks; int curZ = z;
double d8 = d2 + (d3 - d2) * l / numberOfBlocks; boolean stopWalk = false;
double d9 = rand.nextDouble() * numberOfBlocks / 16.0D;
double d10 = (MathHelper.sin(l * (float) Math.PI / numberOfBlocks) + 1.0F) * d9 + 1.0D; for(int step = 0; step <= CORE_MAX_TRIES && !stopWalk; ++step) {
double d11 = (MathHelper.sin(l * (float) Math.PI / numberOfBlocks) + 1.0F) * d9 + 1.0D; curX = rand.nextInt(Math.max(1, maxX - minX)) + minX;
int i1 = MathHelper.floor_double(d6 - d10 / 2.0D); curY = rand.nextInt(Math.max(1, maxY - minY)) + minY;
int j1 = MathHelper.floor_double(d7 - d11 / 2.0D); curZ = rand.nextInt(Math.max(1, maxZ - minZ)) + minZ;
int k1 = MathHelper.floor_double(d8 - d10 / 2.0D);
int l1 = MathHelper.floor_double(d6 + d10 / 2.0D); if (isBlockEmpty(world, curX, curY, curZ)) {
int i2 = MathHelper.floor_double(d7 + d11 / 2.0D); world.setBlock(curX, curY, curZ, block, metadata, 2);
int j2 = MathHelper.floor_double(d8 + d10 / 2.0D); addedBlocks.add(new Location(curX, curY, curZ));
stopWalk = true;
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));
}
}
}
}
} }
} }
} }
return addedBlocks; return addedBlocks;
} }

View file

@ -4,7 +4,9 @@ import java.io.File;
import java.io.FilenameFilter; import java.io.FilenameFilter;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.NavigableMap;
import java.util.Random; import java.util.Random;
import java.util.TreeMap;
import org.w3c.dom.Document; import org.w3c.dom.Document;
import org.w3c.dom.Element; import org.w3c.dom.Element;
@ -23,7 +25,9 @@ public class StructureManager {
private static ArrayList<Star> stars = new ArrayList<Star>(); private static ArrayList<Star> stars = new ArrayList<Star>();
private static ArrayList<Planetoid> moons = new ArrayList<Planetoid>(); private static ArrayList<Planetoid> moons = new ArrayList<Planetoid>();
private static ArrayList<Planetoid> gasClouds = new ArrayList<Planetoid>(); private static ArrayList<Planetoid> gasClouds = new ArrayList<Planetoid>();
private static ArrayList<Asteroid> asteroids = new ArrayList<Asteroid>(); private static ArrayList<Asteroid> asteroids = new ArrayList<Asteroid>();
private static RandomCollection<Asteroid> randomAsteroids = new RandomCollection<Asteroid>();
public static void loadStructures(String structureConfDir) { public static void loadStructures(String structureConfDir) {
loadStructures(new File(structureConfDir)); loadStructures(new File(structureConfDir));
@ -100,6 +104,20 @@ public class StructureManager {
Asteroid as = new Asteroid(); Asteroid as = new Asteroid();
as.loadFromXmlElement(struct); as.loadFromXmlElement(struct);
asteroids.add(as); 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")) { } else if (type.equalsIgnoreCase("moon")) {
return moons.get(random.nextInt(moons.size())); return moons.get(random.nextInt(moons.size()));
} else if (type.equalsIgnoreCase("asteroid")) { } else if (type.equalsIgnoreCase("asteroid")) {
return asteroids.get(random.nextInt(asteroids.size())); return randomAsteroids.next(random);
} }
} else { } else {
for (Star star : stars) { for (Star star : stars) {
@ -141,4 +159,27 @@ public class StructureManager {
public static DeployableStructure getGasCloud(Random random, final String name) { public static DeployableStructure getGasCloud(Random random, final String name) {
return getStructure(random, name, "cloud"); 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> </shell>
</structure> </structure>
<structure group="asteroid" name="asteroid" coreBlock="minecraft:iron_ore" maxCoreSize="10" minCoreSize="6"> <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">
<shell name="mantle" minThickness="2" maxThickness="10" fillerSets="commonOres,uncommonOres">
<filler weight="10" block="minecraft:stone" /> <filler weight="10" block="minecraft:stone" />
<filler weight="5" block="minecraft:cobblestone" /> <filler weight="5" block="minecraft:cobblestone" />
</shell> </shell>
</structure>
</structure>
<structure group="asteroid" name="SphericalAsteroid" weight="10" coreBlock="minecraft:iron_ore" maxCoreSize="3" minCoreSize="1" coreRad="0.1">
<structure group="asteroid" name="diamondGeode" coreBlock="minecraft:diamond_ore" maxCoreSize="2" minCoreSize="1"> <shell name="mantle" minThickness="5" maxThickness="10" fillerSets="commonOres,uncommonOres">
<shell name="mantle" minThickness="5" maxThickness="25" fillerSets="commonOres">
<filler weight="10" block="minecraft:stone" /> <filler weight="10" block="minecraft:stone" />
<filler weight="5" block="minecraft:cobblestone" /> <filler weight="5" block="minecraft:cobblestone" />
</shell> </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> </warpdriveWorldGeneration>