package net.fourbytes.shadow.genlevel;
import com.badlogic.gdx.math.MathUtils;
import com.badlogic.gdx.math.Rectangle;
import com.badlogic.gdx.math.Vector2;
import com.badlogic.gdx.utils.Array;
import com.badlogic.gdx.utils.IntIntMap;
import com.badlogic.gdx.utils.LongArray;
import net.fourbytes.shadow.*;
import net.fourbytes.shadow.blocks.BlockType;
import net.fourbytes.shadow.entities.Cursor;
import net.fourbytes.shadow.entities.Player;
import net.fourbytes.shadow.map.DataChunk;
import net.fourbytes.shadow.map.IsSaveable;
import net.fourbytes.shadow.map.MapObject;
import net.fourbytes.shadow.map.ShadowMap;
import net.fourbytes.shadow.mod.ModManager;
import net.fourbytes.shadow.utils.AsyncThread;
import net.fourbytes.shadow.utils.gdx.LongIntMap;
import java.lang.reflect.Field;
import java.util.Random;
public class GenLevel extends Level {
@IsSaveable
public int seed = (int) (Math.random()*(Integer.MAX_VALUE/2));
/*
* Seed list:
* 515421
* 65525
* -229985452 generates hello
* -147909648 generates world
*
*/
public Random rand = new Random(seed);
@IsSaveable
public boolean storeAuto = true;
protected boolean storeAutoDelayed = false;
public boolean storeForce = false;
//ShadowMap actually stores generated chunks. They just need to be re-added later on...
//TODO add chunks to this list after loaded from file
public LongArray generated = new LongArray();
//ShadowMap actually stores that
public LongArray stored = new LongArray();
//only temporary, so no Saveable annotation
public LongIntMap ageChunks = new LongIntMap();
//only temporary, so no Saveable annotation
public int ageCurrent = 0;
@IsSaveable
public int ageOffset = 60*5;//TODO Change for production builds!
//Seeded semi-randoms / maths should "save" this.
public IntIntMap xHeight = new IntIntMap();
//Seeded semi-randoms / maths should "save" this.
public IntIntMap xStone = new IntIntMap();
//TODO saveable?
public CaveGen cavegen = new DefaultCaveGen(this);
public ShadowMap mapLoaded;
public GenLevel() {
map = new ShadowMap();
map.file = Shadow.getDir("saves").child("tmpgen.smf");
fillLayer(0);
fillLayer(1);
float d = 0.5f;
layers.get(0).tint.set(d, d, d, 1f);
//generateChunks(-2*Chunk.size, 2*Chunk.size, 2*Chunk.size, 2*Chunk.size);
Player p = new Player(new Vector2(0f, -5f), layers.get(1));
layers.get(1).add(p);
player = p;
c = new Cursor(new Vector2(0f, 0f), layers.get(1));
System.gc();
dirtify = true;
ready = true;
}
public boolean generateChunks(int fromx, int tox, int fromy, int toy) {
fromx = (fromx/Chunk.size)*Chunk.size;
tox = (tox/Chunk.size)*Chunk.size;
fromy = (fromy/Chunk.size)*Chunk.size;
toy = (toy/Chunk.size)*Chunk.size;
boolean generated = false;
for (int xx = fromx; xx < tox; xx+=Chunk.size) {
for (int yy = fromy; yy < toy; yy+=Chunk.size) {
if (generateChunk(xx, yy)) {
generated = true;
}
}
}
return generated;
}
public boolean generateChunk(int xx, int yy) {
xx = (xx/Chunk.size)*Chunk.size;
yy = (yy/Chunk.size)*Chunk.size;
//Check whether current chunk already generated
long chunkc = Coord.get(xx / Chunk.size, yy / Chunk.size);
ageChunks.put(chunkc, ageCurrent);
if (stored.contains(chunkc)) {
loadChunk(chunkc);
return false;
}
if (generated.contains(chunkc)) {
return false;
} else {
generated.add(chunkc);
}
dirtify = false;
//"clean" the chunk (could be dirty because of f.e. water off-screen)
Chunk chunk;
chunk = mainLayer.chunkmap.get(chunkc);
if (chunk != null) {
chunk.dirty = 0;
}
chunk = layers.get(1).chunkmap.get(chunkc);
if (chunk != null) {
chunk.dirty = 0;
}
chunk = layers.get(0).chunkmap.get(chunkc);
if (chunk != null) {
chunk.dirty = 0;
}
//rand.setSeed(seed + Coord.get(xx, xx));
//For each block in the current row
for (int x = xx; x < xx + Chunk.size; x++) {
rand.setSeed(seed + Coord.get(x, xx));
//Init X variables
//TODO better generation
xHeight.put(x, xHeight.get(x, (int) (
5f * MathUtils.sinDeg(x + rand.nextInt(24) - 12)
- 4f * MathUtils.cosDeg(x * 0.25f + rand.nextInt(8) - 4)
+ 2f * MathUtils.sinDeg(x * 2f + rand.nextInt(32) - 16)
- 4f * MathUtils.cosDeg(MathUtils.sinDeg(x * 0.75f + rand.nextInt(48) - 28) * 90f)
) + 7));
xStone.put(x, xStone.get(x, rand.nextInt(5) + xHeight.get(x, 0)));
rand.setSeed(seed + Coord.get(x, yy));
//For each block in current chunk
for (int y = yy; y < yy + Chunk.size; y++) {
//Remove already existing blocks
Array<Block> al = layers.get(1).get(Coord.get(x, y));
if (al != null) {
while (al.size > 0) {
layers.get(1).remove(al.pop());
}
}
al = layers.get(0).get(Coord.get(x, y));
if (al != null) {
while (al.size > 0) {
layers.get(0).remove(al.pop());
}
}
//Generate the tile at X, Y
boolean cangen = ModManager.generateTile(GenLevel.this, xx, x, y, 1);
if (cangen) {
generateTile(xx, x, y, 1);
}
/*//Add / remove /* on beginning of line to disable / enable debugging.
if (y == xHeight.get(x, 0)) {
Block block = BlockType.getInstance("BlockDebug", x, y, layers.get(1));
block.alpha = 0.25f;
block.blending = true;
layers.get(1).add(block);
}
/*
*/
}
}
dirtify = true;
return true;
}
public void generateTile(int xx, int x, int y, int fg) {
Layer lfg = layers.get(fg);
Layer lbg = layers.get(fg-1);
if (2 <= y && y < xHeight.get(x, 0)) {
//Generate water.
lfg.add(BlockType.getInstance("BlockWater", x, y, lfg));
return;
}
if (y == xHeight.get(x, 0)) {
//Generate surface (grass)
lfg.add(BlockType.getInstance("BlockGrass", x, y, lfg));
lbg.add(BlockType.getInstance("BlockGrass", x, y, lbg));
return;
}
if (y > xHeight.get(x, 0)) {
if (y < xStone.get(x, 0)) {
//Generate surface (dirt)
lfg.add(BlockType.getInstance("BlockDirt", x, y, lfg));
lbg.add(BlockType.getInstance("BlockDirt", x, y, lbg));
} else {
//Generate everything underground.
cavegen.generateFG(xx, x, y, fg);
cavegen.generateBG(xx, x, y, fg);
//lfg.add(BlockType.getInstance("BlockStone", x, y, lfg));
//lbg.add(BlockType.getInstance("BlockStone", x, y, lbg));
}
}
}
public void loadChunk(long chunkc) {
AsyncThread thread = map.asyncThread(false);
if (thread != null && thread.left > 0) {
return;
}
if (!stored.contains(chunkc)) {
return;
}
DataChunk chunk = map.chunkmap.get(chunkc);
if (chunk == null) {
if (mapLoaded == null) {
mapLoaded = map;
} else if (mapLoaded.timestamp < map.timestamp) {
mapLoaded = ShadowMap.loadFile(map.file);
}
chunk = mapLoaded.chunkmap.get(chunkc);
if (chunk == null) {
mapLoaded = ShadowMap.loadFile(map.file);
chunk = mapLoaded.chunkmap.get(chunkc);
if (chunk == null) {
stored.removeValue(chunkc);
return;
}
}
map.chunkmap.put(chunkc, chunk);
}
ageChunks.put(chunkc, ageCurrent);
for (int i = 0; i < chunk.objects.size; i++) {
MapObject mo = chunk.objects.items[i];
GameObject go = ShadowMap.convert(mo, this);
if (!(go instanceof Player)) {
go.layer.add(go);
}
}
stored.removeValue(chunkc);
generated.add(chunkc);
}
/**
* @return 0 when already stored; 1 when to clear; 3 when to save
*/
public int saveChunk(long chunkc) {
if (stored.contains(chunkc)) {
return 0;//00
}
DataChunk chunk = DataChunk.create(Coord.getX(chunkc), Coord.getY(chunkc), this, true);
if (chunk == null) {
generated.removeValue(chunkc);
return 1;//01
}
map.chunkmap.put(chunkc, chunk);
stored.add(chunkc);
generated.removeValue(chunkc);
return 3;//11
}
public void clearChunk(long chunkc) {
Chunk chunk = mainLayer.chunkmap.get(chunkc);
if (chunk == null) {
return;
}
while (chunk.blocks.size > 0) {
GameObject go = chunk.blocks.items[0];
go.layer.remove(go);
}
//TODO remove entities in chunk, not added in chunk
/*
while (chunk.entities.size > 0) {
chunk.layer.remove(chunk.entities.pop());
}
*/
}
@Override
public void tick(float delta) {
super.tick(delta);
Rectangle vp = Shadow.cam.camrec;
ageCurrent++;
Vector2 ppos = player.pos;
generateChunks((int)(ppos.x - vp.width*2f), (int)(ppos.x + vp.width*2f),
(int)(ppos.y - vp.height*2f), (int)(ppos.y + vp.height*2f));
if ((storeAuto && ageCurrent%ageOffset == 0) || storeForce || storeAutoDelayed) {
storeForce = false;
AsyncThread thread = map.asyncThread();
if (thread.left > 0) {
//System.out.println("Left: "+thread.left);
storeAutoDelayed = true;
} else {
storeAutoDelayed = false;
thread.queue(new Runnable() {
@Override
public void run() {
int chunks = 0;
for (int i = 0; i < generated.size; i++) {
long chunkc = generated.items[i];
int age = ageChunks.get(chunkc, ageCurrent);
if (age < ageCurrent - ageOffset) {
int state = saveChunk(chunkc);
if ((state & 1) == 1) {
clearChunk(chunkc);
}
if ((state & 2) == 2) {
chunks++;
}
}
}
if (chunks > 0) {
Field[] fields = GenLevel.this.getClass().getFields();
Field.setAccessible(fields, true);
for (Field field : fields) {
IsSaveable saveable = field.getAnnotation(IsSaveable.class);
if (saveable != null) {
try {
map.params.put(field.getName(), field.get(GenLevel.this));
} catch (Exception e) {
e.printStackTrace();
}
}
}
System.out.println("Saving " + chunks + " chunks...");
map.update(map.file, true);
}
}
});
}
}
}
}