|
|
|
package mightypork.rogue.world.level;
|
|
|
|
|
|
|
|
|
|
|
|
import java.io.IOException;
|
|
|
|
import java.util.Collection;
|
|
|
|
import java.util.HashMap;
|
|
|
|
import java.util.HashSet;
|
|
|
|
import java.util.Map;
|
|
|
|
import java.util.Set;
|
|
|
|
|
|
|
|
import mightypork.rogue.world.Coord;
|
|
|
|
import mightypork.rogue.world.Sides;
|
|
|
|
import mightypork.rogue.world.World;
|
|
|
|
import mightypork.rogue.world.entity.Entities;
|
|
|
|
import mightypork.rogue.world.entity.Entity;
|
|
|
|
import mightypork.rogue.world.pathfinding.FillContext;
|
|
|
|
import mightypork.rogue.world.pathfinding.FloodFill;
|
|
|
|
import mightypork.rogue.world.tile.Tile;
|
|
|
|
import mightypork.rogue.world.tile.TileModel;
|
|
|
|
import mightypork.rogue.world.tile.TileType;
|
|
|
|
import mightypork.rogue.world.tile.Tiles;
|
|
|
|
import mightypork.util.files.ion.IonBinary;
|
|
|
|
import mightypork.util.files.ion.IonBundle;
|
|
|
|
import mightypork.util.files.ion.IonInput;
|
|
|
|
import mightypork.util.files.ion.IonOutput;
|
|
|
|
import mightypork.util.logging.Log;
|
|
|
|
import mightypork.util.math.noise.NoiseGen;
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* One level of the dungeon
|
|
|
|
*
|
|
|
|
* @author MightyPork
|
|
|
|
*/
|
|
|
|
public class Level implements MapAccess, IonBinary {
|
|
|
|
|
|
|
|
public static final int ION_MARK = 53;
|
|
|
|
|
|
|
|
private final Coord size = Coord.zero();
|
|
|
|
private World world;
|
|
|
|
|
|
|
|
private final Coord enterPoint = Coord.zero();
|
|
|
|
|
|
|
|
/** Array of tiles [y][x] */
|
|
|
|
private Tile[][] tiles;
|
|
|
|
|
|
|
|
private final Map<Integer, Entity> entityMap = new HashMap<>();
|
|
|
|
private final Set<Entity> entitySet = new HashSet<>();
|
|
|
|
|
|
|
|
/** Level seed (used for generation and tile variation) */
|
|
|
|
public long seed;
|
|
|
|
|
|
|
|
private transient NoiseGen noiseGen;
|
|
|
|
|
|
|
|
|
|
|
|
public Level() {
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public Level(int width, int height) {
|
|
|
|
size.setTo(width, height);
|
|
|
|
buildArray();
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void buildArray()
|
|
|
|
{
|
|
|
|
this.tiles = new Tile[size.y][size.x];
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public void fill(short id)
|
|
|
|
{
|
|
|
|
fill(Tiles.get(id));
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public void fill(TileModel model)
|
|
|
|
{
|
|
|
|
for (final Coord c = Coord.zero(); c.x < size.x; c.x++) {
|
|
|
|
for (c.y = 0; c.y < size.y; c.y++) {
|
|
|
|
setTile(c, model.createTile());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public final Tile getTile(Coord pos)
|
|
|
|
{
|
|
|
|
if (!pos.isInRange(0, 0, size.x - 1, size.y - 1)) return Tiles.NULL.createTile(); // out of range
|
|
|
|
|
|
|
|
return tiles[pos.y][pos.x];
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public final void setTile(Coord pos, TileModel model)
|
|
|
|
{
|
|
|
|
setTile(pos, model.createTile());
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public final void setTile(Coord pos, int tileId)
|
|
|
|
{
|
|
|
|
setTile(pos, Tiles.create(tileId));
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public final void setTile(Coord pos, Tile tile)
|
|
|
|
{
|
|
|
|
if (!pos.isInRange(0, 0, size.x - 1, size.y - 1)) {
|
|
|
|
Log.w("Invalid tile coord to set: " + pos + ", map size: " + size);
|
|
|
|
return; // out of range
|
|
|
|
}
|
|
|
|
|
|
|
|
tiles[pos.y][pos.x] = tile;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public final int getWidth()
|
|
|
|
{
|
|
|
|
return size.x;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public final int getHeight()
|
|
|
|
{
|
|
|
|
return size.y;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public void setSeed(long seed)
|
|
|
|
{
|
|
|
|
this.seed = seed;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public long getSeed()
|
|
|
|
{
|
|
|
|
return seed;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void load(IonInput in) throws IOException
|
|
|
|
{
|
|
|
|
// -- metadata --
|
|
|
|
final IonBundle ib = in.readBundle();
|
|
|
|
seed = ib.get("seed", 0L);
|
|
|
|
ib.loadBundled("size", size);
|
|
|
|
ib.loadBundled("enter_point", enterPoint);
|
|
|
|
|
|
|
|
|
|
|
|
// -- binary data --
|
|
|
|
|
|
|
|
// load tiles
|
|
|
|
buildArray();
|
|
|
|
|
|
|
|
for (final Coord c = Coord.zero(); c.x < size.x; c.x++) {
|
|
|
|
for (c.y = 0; c.y < size.y; c.y++) {
|
|
|
|
setTile(c, Tiles.loadTile(in));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// load entities
|
|
|
|
Entities.loadEntities(in, entitySet);
|
|
|
|
|
|
|
|
// prepare entities
|
|
|
|
for (final Entity ent : entitySet) {
|
|
|
|
ent.setLevel(this);
|
|
|
|
occupyTile(ent.pos.getCoord());
|
|
|
|
entityMap.put(ent.getEntityId(), ent);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void save(IonOutput out) throws IOException
|
|
|
|
{
|
|
|
|
// -- metadata --
|
|
|
|
final IonBundle ib = new IonBundle();
|
|
|
|
ib.put("seed", seed);
|
|
|
|
ib.putBundled("size", size);
|
|
|
|
ib.putBundled("enter_point", enterPoint);
|
|
|
|
out.writeBundle(ib);
|
|
|
|
|
|
|
|
// -- binary data --
|
|
|
|
|
|
|
|
// tiles
|
|
|
|
for (final Coord c = Coord.zero(); c.x < size.x; c.x++) {
|
|
|
|
for (c.y = 0; c.y < size.y; c.y++) {
|
|
|
|
Tiles.saveTile(out, getTile(c));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Entities.saveEntities(out, entitySet);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public short getIonMark()
|
|
|
|
{
|
|
|
|
return ION_MARK;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public void update(double delta)
|
|
|
|
{
|
|
|
|
// just update them all
|
|
|
|
for (final Coord c = Coord.zero(); c.x < size.x; c.x++) {
|
|
|
|
for (c.y = 0; c.y < size.y; c.y++) {
|
|
|
|
getTile(c).update(this, delta);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
for (final Entity e : entitySet) {
|
|
|
|
e.update(delta);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public NoiseGen getNoiseGen()
|
|
|
|
{
|
|
|
|
if (noiseGen == null) {
|
|
|
|
noiseGen = new NoiseGen(0.2, 0, 0.5, 1, seed);
|
|
|
|
}
|
|
|
|
|
|
|
|
return noiseGen;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public Entity getEntity(int eid)
|
|
|
|
{
|
|
|
|
return entityMap.get(eid);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public void addEntity(Entity entity)
|
|
|
|
{
|
|
|
|
if (entityMap.containsKey(entity.getEntityId())) {
|
|
|
|
Log.w("Entity already in level.");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
entityMap.put(entity.getEntityId(), entity);
|
|
|
|
entitySet.add(entity);
|
|
|
|
|
|
|
|
// join to level & world
|
|
|
|
entity.setLevel(this);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public void removeEntity(Entity entity)
|
|
|
|
{
|
|
|
|
entityMap.remove(entity.getEntityId());
|
|
|
|
entitySet.remove(entity);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public void removeEntity(int eid)
|
|
|
|
{
|
|
|
|
final Entity removed = entityMap.remove(eid);
|
|
|
|
entitySet.remove(removed);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public boolean isWalkable(Coord pos)
|
|
|
|
{
|
|
|
|
final Tile t = getTile(pos);
|
|
|
|
|
|
|
|
return t.isWalkable() && !t.isOccupied();
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Mark tile as occupied by an entity
|
|
|
|
*/
|
|
|
|
public void occupyTile(Coord pos)
|
|
|
|
{
|
|
|
|
getTile(pos).setOccupied(true);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Mark tile as free (no longet occupied)
|
|
|
|
*/
|
|
|
|
public void freeTile(Coord pos)
|
|
|
|
{
|
|
|
|
getTile(pos).setOccupied(false);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public Collection<Entity> getEntities()
|
|
|
|
{
|
|
|
|
return entitySet;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public void setEnterPoint(Coord pos)
|
|
|
|
{
|
|
|
|
this.enterPoint.setTo(pos);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public Coord getEnterPoint()
|
|
|
|
{
|
|
|
|
return enterPoint;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public World getWorld()
|
|
|
|
{
|
|
|
|
return world;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public void setWorld(World world)
|
|
|
|
{
|
|
|
|
this.world = world;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public void explore(Coord center)
|
|
|
|
{
|
|
|
|
Collection<Coord> filled = new HashSet<>();
|
|
|
|
|
|
|
|
FloodFill.fill(center, exploreFc, filled);
|
|
|
|
|
|
|
|
for (Coord c : filled) {
|
|
|
|
getTile(c).setExplored();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private FillContext exploreFc = new FillContext() {
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public Coord[] getSpreadSides()
|
|
|
|
{
|
|
|
|
return Sides.allSides;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public double getMaxDistance()
|
|
|
|
{
|
|
|
|
return 5.4;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public boolean canSpreadFrom(Coord pos)
|
|
|
|
{
|
|
|
|
Tile t = getTile(pos);
|
|
|
|
return t.isWalkable() && t.getType() != TileType.DOOR;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public boolean canEnter(Coord pos)
|
|
|
|
{
|
|
|
|
Tile t = getTile(pos);
|
|
|
|
return !t.isNull();
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public boolean forceSpreadStart()
|
|
|
|
{
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
}
|