Procedural zone generation

This commit is contained in:
Anuken
2019-04-09 22:52:38 -04:00
parent d82c24616f
commit daccfa5fe3
10 changed files with 262 additions and 61 deletions

View File

@ -319,6 +319,7 @@ error.io = Network I/O error.
error.any = Unknown network error.
zone.groundZero.name = Ground Zero
zone.desertWastes.name = Desert Wastes
zone.craters.name = The Craters
zone.frozenForest.name = Frozen Forest
zone.ruinousShores.name = Ruinous Shores

View File

@ -2,6 +2,7 @@ package io.anuke.mindustry.content;
import io.anuke.mindustry.game.ContentList;
import io.anuke.mindustry.game.Rules;
import io.anuke.mindustry.maps.generators.DesertWastesGenerator;
import io.anuke.mindustry.maps.generators.MapGenerator;
import io.anuke.mindustry.maps.generators.MapGenerator.Decoration;
import io.anuke.mindustry.type.*;
@ -9,7 +10,7 @@ import io.anuke.mindustry.world.Block;
public class Zones implements ContentList{
public static Zone
groundZero, desertThing,
groundZero, desertWastes,
craters, frozenForest, ruinousShores, stainedMountains,
overgrowth, infestedIslands,
desolateRift, nuclearComplex;
@ -31,19 +32,18 @@ public class Zones implements ContentList{
}};
}};
/*
desertThing = new Zone("desertThing", new DesertThingGenerator(240, 240)){{
desertWastes = new Zone("desertWastes", new DesertWastesGenerator(260, 260)){{
startingItems = ItemStack.list(Items.copper, 200);
alwaysUnlocked = true;
conditionWave = 5;
launchPeriod = 5;
resources = new Item[]{Items.copper, Items.scrap, Items.lead};
conditionWave = 10;
zoneRequirements = ZoneRequirement.with(groundZero, 10);
blockRequirements = new Block[]{Blocks.router};
resources = new Item[]{Items.copper, Items.lead, Items.coal};
rules = () -> new Rules(){{
waves = true;
waveTimer = true;
waveSpacing = 60 * 60 * 2;
waveSpacing = 60 * 60 * 1.5f;
}};
}};*/
}};
craters = new Zone("craters", new MapGenerator("craters", 1).dist(0).decor(new Decoration(Blocks.snow, Blocks.sporeCluster, 0.01))){{
startingItems = ItemStack.list(Items.copper, 200);

View File

@ -3,7 +3,7 @@ package io.anuke.mindustry.io;
import io.anuke.arc.collection.*;
import io.anuke.arc.files.FileHandle;
import io.anuke.mindustry.Vars;
import io.anuke.mindustry.io.versions.Save16;
import io.anuke.mindustry.io.versions.Save1;
import java.io.*;
import java.util.zip.DeflaterOutputStream;
@ -15,9 +15,7 @@ import static io.anuke.mindustry.Vars.*;
public class SaveIO{
public static final IntArray breakingVersions = IntArray.with(47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 63);
public static final IntMap<SaveFileVersion> versions = new IntMap<>();
public static final Array<SaveFileVersion> versionArray = Array.with(
new Save16()
);
public static final Array<SaveFileVersion> versionArray = Array.with(new Save1());
static{
for(SaveFileVersion version : versionArray){

View File

@ -11,10 +11,10 @@ import java.io.*;
import static io.anuke.mindustry.Vars.*;
public class Save16 extends SaveFileVersion{
public class Save1 extends SaveFileVersion{
public Save16(){
super(16);
public Save1(){
super(1);
}
@Override

View File

@ -1,18 +1,26 @@
package io.anuke.mindustry.maps.generators;
import io.anuke.arc.collection.Array;
import io.anuke.arc.collection.*;
import io.anuke.arc.function.IntPositionConsumer;
import io.anuke.arc.math.Mathf;
import io.anuke.arc.math.geom.Geometry;
import io.anuke.arc.math.geom.Point2;
import io.anuke.arc.util.Structs;
import io.anuke.arc.util.noise.Simplex;
import io.anuke.mindustry.content.Blocks;
import io.anuke.mindustry.type.Item;
import io.anuke.mindustry.world.Tile;
import io.anuke.mindustry.world.*;
import io.anuke.mindustry.world.blocks.Floor;
public class BasicGenerator extends RandomGenerator{
private Array<Item> ores;
private Simplex sim = new Simplex();
private Simplex sim2 = new Simplex();
import java.util.PriorityQueue;
public BasicGenerator(int width, int height, Item... ores){
public abstract class BasicGenerator extends RandomGenerator{
protected static final DistanceHeuristic manhattan = (x1, y1, x2, y2) -> Math.abs(x1 - x2) + Math.abs(y1 - y2);
protected Array<Block> ores;
protected Simplex sim = new Simplex();
protected Simplex sim2 = new Simplex();
public BasicGenerator(int width, int height, Block... ores){
super(width, height);
this.ores = Array.with(ores);
}
@ -25,33 +33,189 @@ public class BasicGenerator extends RandomGenerator{
super.generate(tiles);
}
@Override
public void generate(int x, int y){
floor = Blocks.stone;
public void ores(Tile[][] tiles){
pass(tiles, (x, y) -> {
if(ores != null){
int offsetX = x - 4, offsetY = y + 23;
for(int i = ores.size - 1; i >= 0; i--){
Block entry = ores.get(i);
if(Math.abs(0.5f - sim.octaveNoise2D(2, 0.7, 1f / (50 + i * 2), offsetX, offsetY + i*999)) > 0.23f &&
Math.abs(0.5f - sim2.octaveNoise2D(1, 1, 1f / (40 + i * 4), offsetX, offsetY - i*999)) > 0.32f){
ore = entry;
break;
}
}
}
});
}
if(ores != null){
int offsetX = x - 4, offsetY = y + 23;
for(int i = ores.size - 1; i >= 0; i--){
Item entry = ores.get(i);
if(Math.abs(0.5f - sim.octaveNoise2D(2, 0.7, 1f / (50 + i * 2), offsetX, offsetY)) > 0.23f &&
Math.abs(0.5f - sim2.octaveNoise2D(1, 1, 1f / (40 + i * 4), offsetX, offsetY)) > 0.32f){
public void terrain(Tile[][] tiles, Block dst, float mag, float cmag){
pass(tiles, (x, y) -> {
double rocks = sim.octaveNoise2D(5, 0.5, 1f / 60f, x, y) * mag
+ Mathf.dst((float)x / width, (float)y / height, 0.5f, 0.5f) * cmag;
//floor = OreBlock.get(floor, entry);
break;
double edgeDist = Math.min(x, Math.min(y, Math.min(Math.abs(x - (width - 1)), Math.abs(y - (height - 1)))));
double transition = 5;
if(edgeDist < transition){
rocks += (transition - edgeDist) / transition / 1.5;
}
if(rocks > 0.9){
block =dst;
}
});
}
public void noise(Tile[][] tiles, Block floor, Block block, int octaves, float falloff, float scl, float threshold){
pass(tiles, (x, y) -> {
if(sim.octaveNoise2D(octaves, falloff, 1f / scl, x, y) > threshold){
Tile tile = tiles[x][y];
this.floor = floor;
if(tile.block().solid){
this.block = block;
}
}
});
}
public void distort(Tile[][] tiles, float scl, float mag){
Block[][] blocks = new Block[width][height];
Floor[][] floors = new Floor[width][height];
each((x, y) -> {
float cx = x + noise(x, y, scl, mag) - mag / 2f, cy = y + noise(x, y + 1525215f, scl, mag) - mag / 2f;
Tile other = tiles[Mathf.clamp((int)cx, 0, width-1)][Mathf.clamp((int)cy, 0, height-1)];
blocks[x][y] = other.block();
floors[x][y] = other.floor();
});
pass(tiles, (x, y) -> {
floor = floors[x][y];
block = blocks[x][y];
});
}
public void each(IntPositionConsumer r){
for(int x = 0; x < width; x++){
for(int y = 0; y < height; y++){
r.accept(x, y);
}
}
}
protected float noise(float x, float y, float scl, float mag){
return (float)sim2.octaveNoise2D(1f, 0f, 1f / scl, x + 0x361266f, y + 0x251259f) * mag;
}
public void pass(Tile[][] tiles, IntPositionConsumer r){
for(int x = 0; x < width; x++){
for(int y = 0; y < height; y++){
floor = tiles[x][y].floor();
block = tiles[x][y].block();
ore = tiles[x][y].ore();
r.accept(x, y);
tiles[x][y] = new Tile(x, y, floor.id, block.id);
tiles[x][y].setOre(ore);
}
}
}
public void brush(Tile[][] tiles, Array<Tile> path, int rad){
path.each(tile -> erase(tiles, tile.x, tile.y, rad));
}
public void erase(Tile[][] tiles, int cx, int cy, int rad){
for(int x = -rad; x <= rad; x++){
for(int y = -rad; y <= rad; y++){
int wx = cx + x, wy = cy + y;
if(Structs.inBounds(wx, wy, width, height) && Mathf.dst(x, y, 0, 0) <= rad){
Tile other = tiles[wx][wy];
other.setBlock(Blocks.air);
}
}
}
}
public Array<Tile> pathfind(Tile[][] tiles, int startX, int startY, int endX, int endY, TileHueristic th, DistanceHeuristic dh){
Tile start = tiles[startX][startY];
Tile end = tiles[endX][endY];
GridBits closed = new GridBits(width, height);
IntFloatMap costs = new IntFloatMap();
PriorityQueue<Tile> queue = new PriorityQueue<>((a, b) -> Float.compare(costs.get(a.pos(), 0f) + dh.cost(a.x, a.y, end.x, end.y), costs.get(b.pos(), 0f) + dh.cost(b.x, b.y, end.x, end.y)));
queue.add(start);
boolean found = false;
while(!queue.isEmpty()){
Tile next = queue.poll();
float baseCost = costs.get(next.pos(), 0f);
if(next == end){
found = true;
break;
}
closed.set(next.x, next.y);
for(Point2 point : Geometry.d4){
int newx = next.x + point.x, newy = next.y + point.y;
if(Structs.inBounds(newx, newy, width, height)){
Tile child = tiles[newx][newy];
if(!closed.get(child.x, child.y)){
closed.set(child.x, child.y);
child.setRotation(child.relativeTo(next.x, next.y));
costs.put(child.pos(), th.cost(child) + baseCost);
queue.add(child);
}
}
}
}
//rock outcrops
double rocks = sim.octaveNoise2D(3, 0.7, 1f / 70f, x, y);
double edgeDist = Math.min(x, Math.min(y, Math.min(Math.abs(x - (width - 1)), Math.abs(y - (height - 1)))));
double transition = 8;
if(edgeDist < transition){
rocks += (transition - edgeDist) / transition / 1.5;
Array<Tile> out = new Array<>();
if(!found) return out;
Tile current = end;
while(current != start){
out.add(current);
Point2 p = Geometry.d4(current.getRotation());
current = tiles[current.x + p.x][current.y + p.y];
}
if(rocks > 0.64){
block = Blocks.rocks;
out.reverse();
return out;
}
public void inverseFloodFill(Tile[][] tiles, Tile start, Block block){
IntArray arr = new IntArray();
arr.add(start.pos());
while(!arr.isEmpty()){
int i = arr.pop();
int x = Pos.x(i), y = Pos.y(i);
tiles[x][y].cost = 2;
for(Point2 point : Geometry.d4){
int newx = x + point.x, newy = y + point.y;
if(Structs.inBounds(newx, newy, width, height)){
Tile child = tiles[newx][newy];
if(child.block() == Blocks.air && child.cost != 2){
child.cost = 2;
arr.add(child.pos());
}
}
}
}
for(int x = 0; x < width; x ++){
for(int y = 0; y < height; y++){
Tile tile = tiles[x][y];
if(tile.cost != 2 && tile.block() == Blocks.air){
tile.setBlock(block);
}
}
}
}
public interface DistanceHeuristic{
float cost(int x1, int y1, int x2, int y2);
}
public interface TileHueristic{
float cost(Tile tile);
}
}

View File

@ -1,13 +0,0 @@
package io.anuke.mindustry.maps.generators;
public class DesertThingGenerator extends RandomGenerator{
public DesertThingGenerator(int width, int height){
super(width, height);
}
@Override
public void generate(int x, int y){
}
}

View File

@ -0,0 +1,41 @@
package io.anuke.mindustry.maps.generators;
import io.anuke.arc.math.Mathf;
import io.anuke.mindustry.content.Blocks;
import io.anuke.mindustry.world.Tile;
public class DesertWastesGenerator extends BasicGenerator{
public DesertWastesGenerator(int width, int height){
super(width, height, Blocks.oreCopper, Blocks.oreLead, Blocks.oreCoal, Blocks.oreCopper);
}
@Override
public void generate(int x, int y){
floor = Blocks.sand;
}
@Override
public void decorate(Tile[][] tiles){
ores(tiles);
terrain(tiles, Blocks.sandRocks, 1.5f, 0.9f);
int rand = 40;
int border = 25;
int spawnX = Mathf.clamp(30 + Mathf.range(rand), border, width - border), spawnY = Mathf.clamp(30 + Mathf.range(rand), border, height - border);
int endX = Mathf.clamp(width - 30 + Mathf.range(rand), border, width - border), endY = Mathf.clamp(height - 30 + Mathf.range(rand), border, height - border);
brush(tiles, pathfind(tiles, spawnX, spawnY, endX, endY, tile -> tile.solid() ? 5f : 0f, manhattan), 6);
brush(tiles, pathfind(tiles, spawnX, spawnY, endX, endY, tile -> tile.solid() ? 4f : 0f + (float)sim.octaveNoise2D(1, 1, 1f / 40f, tile.x, tile.y) * 20, manhattan), 5);
erase(tiles, endX, endY, 10);
erase(tiles, spawnX, spawnY, 20);
distort(tiles, 20f, 4f);
inverseFloodFill(tiles, tiles[spawnX][spawnY], Blocks.sandRocks);
noise(tiles, Blocks.darksand, Blocks.duneRocks, 5, 0.7f, 120f, 0.5f);
tiles[endX][endY].setBlock(Blocks.spawn);
loadout.setup(spawnX, spawnY);
}
}

View File

@ -5,6 +5,7 @@ import io.anuke.mindustry.world.Tile;
public abstract class Generator{
public int width, height;
protected Loadout loadout;
public Generator(int width, int height){
this.width = width;
@ -15,7 +16,7 @@ public abstract class Generator{
}
public void init(Loadout loadout){
this.loadout = loadout;
}
public abstract void generate(Tile[][] tiles);

View File

@ -26,7 +26,6 @@ public class MapGenerator extends Generator{
private Map map;
private String mapName;
private Array<Decoration> decorations = Array.with(new Decoration(Blocks.stone, Blocks.rock, 0.003f));
private Loadout loadout;
/** How much the landscape is randomly distorted. */
public float distortion = 3;
/**

View File

@ -1,13 +1,18 @@
package io.anuke.mindustry.maps.generators;
import io.anuke.arc.collection.ObjectMap;
import io.anuke.mindustry.content.Blocks;
import io.anuke.mindustry.game.Team;
import io.anuke.mindustry.maps.Map;
import io.anuke.mindustry.world.Block;
import io.anuke.mindustry.world.Tile;
import static io.anuke.mindustry.Vars.customMapDirectory;
import static io.anuke.mindustry.Vars.world;
public abstract class RandomGenerator extends Generator{
protected Block floor;
protected Block block;
protected Block ore;
public RandomGenerator(int width, int height){
super(width, height);
@ -19,15 +24,20 @@ public abstract class RandomGenerator extends Generator{
for(int y = 0; y < height; y++){
floor = Blocks.air;
block = Blocks.air;
ore = Blocks.air;
generate(x, y);
tiles[x][y] = new Tile(x, y, floor.id, block.id);
tiles[x][y].setOre(ore);
}
}
tiles[width / 2][height / 2].setBlock(Blocks.coreShard, Team.blue);
tiles[width / 2][height / 2 - 6].setBlock(Blocks.launchPad, Team.blue);
decorate(tiles);
world.setMap(new Map(customMapDirectory.child("generated"), 0, 0, new ObjectMap<>(), true));
}
public abstract void decorate(Tile[][] tiles);
/**
* Sets {@link #floor} and {@link #block} to the correct values as output.
* Before this method is called, both are set to {@link Blocks#air} as defaults.