package Roguelike.DungeonGeneration;
import java.util.PriorityQueue;
import java.util.Random;
import Roguelike.Global;
import Roguelike.Global.Direction;
import Roguelike.Global.Passability;
import Roguelike.DungeonGeneration.DungeonFileParser.DFPRoom;
import Roguelike.DungeonGeneration.FactionParser.Creature;
import Roguelike.DungeonGeneration.FactionParser.Feature;
import Roguelike.DungeonGeneration.FactionParser.FeaturePlacementType;
import Roguelike.DungeonGeneration.RoomGenerators.AbstractRoomGenerator;
import Roguelike.Pathfinding.AStarPathfind;
import Roguelike.Pathfinding.BresenhamLine;
import Roguelike.Tiles.Point;
import Roguelike.Util.EnumBitflag;
import com.badlogic.gdx.utils.Array;
// ----------------------------------------------------------------------
public final class Room
{
// ----------------------------------------------------------------------
public static final EnumBitflag<Passability> GeneratorPassability = new EnumBitflag<Passability>( Passability.WALK );
// ----------------------------------------------------------------------
public Direction orientation = Direction.CENTER;
// ----------------------------------------------------------------------
public DFPRoom roomData;
// ----------------------------------------------------------------------
public boolean fromEmptySpace = false;
// ----------------------------------------------------------------------
public int width;
public int height;
// ----------------------------------------------------------------------
public int x;
public int y;
// ----------------------------------------------------------------------
public Array<RoomDoor> doors = new Array<RoomDoor>();
// ----------------------------------------------------------------------
public Symbol[][] roomContents;
// ----------------------------------------------------------------------
public String faction;
// ----------------------------------------------------------------------
private String comparisonString;
// ----------------------------------------------------------------------
public void revertChanges( Random ran, DungeonFileParser dfp )
{
roomData.fillRoom( this, ran, dfp );
comparisonString = null;
doors.clear();
}
// ----------------------------------------------------------------------
public String comparisonString()
{
if ( comparisonString == null )
{
comparisonString = "";
for ( int x = 0; x < width; x++ )
{
for ( int y = 0; y < height; y++ )
{
comparisonString += roomContents[x][y].character;
}
}
}
return comparisonString;
}
// ----------------------------------------------------------------------
public void rotate()
{
Symbol[][] newContents = new Symbol[height][width];
for ( int x = 0; x < width; x++ )
{
for ( int y = 0; y < height; y++ )
{
newContents[y][x] = roomContents[x][y];
}
}
roomContents = newContents;
int temp = height;
height = width;
width = temp;
}
// ----------------------------------------------------------------------
public void flipVertical()
{
for ( int x = 0; x < width; x++ )
{
for ( int y = 0; y < height / 2; y++ )
{
Symbol temp = roomContents[x][y];
roomContents[x][y] = roomContents[x][height - y - 1];
roomContents[x][height - y - 1] = temp;
}
}
}
// ----------------------------------------------------------------------
public void flipHorizontal()
{
for ( int x = 0; x < width / 2; x++ )
{
for ( int y = 0; y < height; y++ )
{
Symbol temp = roomContents[x][y];
roomContents[x][y] = roomContents[width - x - 1][y];
roomContents[width - x - 1][y] = temp;
}
}
}
// ----------------------------------------------------------------------
public void enclose( DungeonFileParser dfp )
{
Symbol wall = dfp.getSymbol( '#' );
wall.resolveExtends( dfp.sharedSymbolMap );
// Ensure solid outer wall
for ( int x = 0; x < width; x++ )
{
for ( int y = 0; y < height; y++ )
{
if ( x == 0 || x == width - 1 || y == 0 || y == height - 1 )
{
if ( isPosDoor( x, y) )
{
}
else
{
roomContents[x][y] = wall;
}
}
if ( roomContents[x][y] == null )
{
roomContents[x][y] = wall;
}
}
}
}
// ----------------------------------------------------------------------
public boolean isPosDoor(int x, int y)
{
for (RoomDoor door : doors)
{
if (door.pos.x == x && door.pos.y == y)
{
return true;
}
}
return false;
}
// ----------------------------------------------------------------------
public void print()
{
System.out.println(this.toString());
}
// ----------------------------------------------------------------------
@Override
public String toString()
{
String s = "";
for (int y = 0; y < height; y++)
{
for (int x = 0; x < width; x++)
{
s += roomContents[x][y].character;
}
s += '\n';
}
return s;
}
// ----------------------------------------------------------------------
public void generateRoomContents( Random ran, DungeonFileParser dfp, Symbol floor, Symbol wall, AbstractRoomGenerator generator )
{
if ( width < dfp.corridorStyle.width+2 && height < dfp.corridorStyle.width+2 )
{
throw new RuntimeException( "Room too small to attach a corridor to!" );
}
Symbol[][] best = null;
int bestFill = 0;
roomContents = new Symbol[width][height];
for ( int i = 0; i < 20; i++ )
{
if ( generator != null )
{
generator.process( roomContents, floor, wall, ran, dfp );
}
else
{
for ( int x = 0; x < width; x++ )
{
for ( int y = 0; y < height; y++ )
{
roomContents[x][y] = floor;
}
}
}
// Ensure solid outer wall
for ( int x = 0; x < width; x++ )
{
for ( int y = 0; y < height; y++ )
{
if ( x == 0 || x == width - 1 || y == 0 || y == height - 1 )
{
roomContents[x][y] = wall;
}
if ( roomContents[x][y] == null )
{
roomContents[x][y] = wall;
}
}
}
// minimise room size
Symbol[][] newRoomContents = RecursiveDockGenerator.minimiseGrid( roomContents, wall );
int nwidth = newRoomContents.length;
int nheight = newRoomContents[0].length;
if (nwidth*nheight > bestFill)
{
bestFill = nwidth*nheight;
best = newRoomContents;
}
if ( nwidth < dfp.corridorStyle.width+2 && nheight < dfp.corridorStyle.width+2 )
{
continue;
}
else if ( nwidth*nheight > ( width * height ) / 2 )
{
break;
}
}
roomContents = best;
width = roomContents.length;
height = roomContents[0].length;
carveDoors( dfp, ran, floor, !generator.ensuresConnectivity );
System.out.println("Generate:");
print();
}
// ----------------------------------------------------------------------
public void carveDoors(DungeonFileParser dfp, Random ran, Symbol floor, boolean digToCenter)
{
boolean canAttachCorridorVertically = width >= dfp.corridorStyle.width + 2;
boolean canAttachCorridorHorizontally = height >= dfp.corridorStyle.width + 2;
// Place corridor connections
// Sides
// 1
// 0 2
// 3
int numDoors = (int) ( Math.max( 0, ran.nextFloat() ) * 3 ) + 1;
for ( int i = 0; i < numDoors; i++ )
{
int doorSide = 0;
if (canAttachCorridorHorizontally && canAttachCorridorVertically) { doorSide = ran.nextInt( 4 ); }
else if (canAttachCorridorHorizontally) { doorSide = ran.nextBoolean() ? 0 : 2; }
else if (canAttachCorridorVertically) { doorSide = ran.nextBoolean() ? 1 : 3; }
int x = 0;
int y = 0;
if ( doorSide == 0 )
{
x = 0;
y = 1 + ran.nextInt( height - ( 1 + dfp.corridorStyle.width ) );
for ( int c = 0; c < dfp.corridorStyle.width; c++ )
{
roomContents[x][y + c] = floor;
}
}
else if ( doorSide == 1 )
{
x = 1 + ran.nextInt( width - ( 1 + dfp.corridorStyle.width ) );
y = 0;
for ( int c = 0; c < dfp.corridorStyle.width; c++ )
{
roomContents[x + c][y] = floor;
}
}
else if ( doorSide == 2 )
{
x = width - 1;
y = 1 + ran.nextInt( height - ( 1 + dfp.corridorStyle.width ) );
for ( int c = 0; c < dfp.corridorStyle.width; c++ )
{
roomContents[x][y + c] = floor;
}
}
else if ( doorSide == 3 )
{
x = 1 + ran.nextInt( width - ( 1 + dfp.corridorStyle.width ) );
y = height - 1;
for ( int c = 0; c < dfp.corridorStyle.width; c++ )
{
roomContents[x + c][y] = floor;
}
}
Array<Point> path = BresenhamLine.lineNoDiag( x, y, width / 2, height / 2 );
for ( Point pos : path )
{
boolean done = false;
if ( roomContents[pos.x][pos.y] == floor )
{
done = true;
}
for ( int ix = 0; ix < dfp.corridorStyle.width; ix++ )
{
for ( int iy = 0; iy < dfp.corridorStyle.width; iy++ )
{
int nx = pos.x + ix;
int ny = pos.y + iy;
if ( nx < width && ny < height )
{
roomContents[nx][ny] = floor;
}
}
}
if ( !digToCenter && done )
{
break;
}
}
Global.PointPool.freeAll( path );
}
}
// ----------------------------------------------------------------------
public void generateRoomContents( Random ran, DungeonFileParser dfp )
{
Symbol floor = roomData != null ? roomData.getSymbol( '.' ) : dfp.sharedSymbolMap.get( '.' );
Symbol wall = roomData != null ? roomData.getSymbol( '#' ) : dfp.sharedSymbolMap.get( '#' );
floor.resolveExtends( dfp.sharedSymbolMap );
wall.resolveExtends( dfp.sharedSymbolMap );
AbstractRoomGenerator generator = dfp.getRoomGenerator( ran );
generateRoomContents( ran, dfp, floor, wall, generator );
}
// ----------------------------------------------------------------------
public void resolveExtends( DungeonFileParser dfp )
{
for (int x = 0; x < width; x++)
{
for (int y = 0; y < height; y++)
{
roomContents[x][y].resolveExtends( dfp.sharedSymbolMap );
}
}
}
// ----------------------------------------------------------------------
private boolean isPosEnclosed( int x, int y )
{
EnumBitflag<Direction> solid = new EnumBitflag<Direction>();
for ( Direction dir : Direction.values() )
{
int x1 = x + dir.getX();
int y1 = y + dir.getY();
boolean collide = x1 >= 0 && y1 >= 0 && x1 < width && y1 < height && !roomContents[x1][y1].isPassable( GeneratorPassability );
if ( collide )
{
solid.setBit( dir );
}
}
// Identify open paths through this pos
// Vertical path
if ( !solid.contains( Direction.NORTH ) && !solid.contains( Direction.SOUTH ) )
{
boolean side1 = solid.contains( Direction.EAST ) || ( solid.contains( Direction.NORTHEAST ) || solid.contains( Direction.SOUTHEAST ) );
boolean side2 = solid.contains( Direction.WEST ) || ( solid.contains( Direction.NORTHWEST ) || solid.contains( Direction.SOUTHWEST ) );
if ( side1 && side2 ) { return true; }
}
// Horizontal path
if ( !solid.contains( Direction.EAST ) && !solid.contains( Direction.WEST ) )
{
boolean side1 = solid.contains( Direction.NORTH ) || ( solid.contains( Direction.NORTHEAST ) || solid.contains( Direction.NORTHWEST ) );
boolean side2 = solid.contains( Direction.SOUTH ) || ( solid.contains( Direction.SOUTHEAST ) || solid.contains( Direction.SOUTHWEST ) );
if ( side1 && side2 ) { return true; }
}
return false;
}
// ----------------------------------------------------------------------
public boolean addFeatures( Random ran, DungeonFileParser dfp, FactionParser faction, int influence, boolean spawnMiniBoss )
{
if ( faction == null ) { return !spawnMiniBoss; }
if ( roomData != null && !roomData.addFactionFeatures ) { return !spawnMiniBoss; }
Symbol[][] roomCopy = new Symbol[width][height];
for ( int x = 0; x < width; x++ )
{
for ( int y = 0; y < height; y++ )
{
roomCopy[x][y] = roomContents[x][y];
}
}
print();
// build the any list
Array<Point> validList = new Array<Point>();
Array<Point> fullList = new Array<Point>();
for ( int x = 0; x < width; x++ )
{
for ( int y = 0; y < height; y++ )
{
if ( roomContents[x][y] != null && roomContents[x][y].isPassable( GeneratorPassability ) && roomContents[x][y].getEnvironmentEntityPassable( GeneratorPassability ) )
{
if ( x > 0 && x < width - 1 && y > 0 && y < height - 1 )
{
Point point = Global.PointPool.obtain().set( x, y );
if (!isPosEnclosed( x, y ))
{
validList.add( point );
}
fullList.add( point );
}
}
}
}
// start placing features
// Do furthest
{
Array<Feature> features = faction.features.get( FeaturePlacementType.FURTHEST );
for ( Feature f : features )
{
// Skip if out of range
if ( influence < f.minRange || influence > f.maxRange )
{
continue;
}
// Build list
PriorityQueue<FeatureTile> furthestList = new PriorityQueue<FeatureTile>();
for ( Point tile : validList )
{
int x = tile.x;
int y = tile.y;
if ( roomContents[x][y].getTileData().canFeature
&& roomContents[x][y].isPassable( GeneratorPassability )
&& ( f.environmentData == null || !roomContents[x][y].hasEnvironmentEntity() ) )
{
furthestList.add( new FeatureTile( tile, doors ) );
}
}
// Skip if no valid tiles
if ( furthestList.size() == 0 )
{
break;
}
// calculate num features to place
int numTilesToPlace = f.getNumTilesToPlace( influence, furthestList.size() );
// Place the features
for ( int i = 0; i < numTilesToPlace; i++ )
{
Point pos = furthestList.poll().pos;
roomContents[pos.x][pos.y] = f.getAsSymbol( roomContents[pos.x][pos.y] );
if ( furthestList.size() == 0 )
{
break;
}
}
}
}
// Do wall
{
Array<Feature> features = faction.features.get( FeaturePlacementType.WALL );
for ( Feature f : features )
{
if ( influence < f.minRange || influence > f.maxRange )
{
continue;
}
// Build list
Array<Point> wallList = new Array<Point>();
for ( Point tile : validList )
{
int x = tile.x;
int y = tile.y;
if ( roomContents[x][y].getTileData().canFeature
&& roomContents[x][y].isPassable( GeneratorPassability )
&& ( f.environmentData == null || !roomContents[x][y].hasEnvironmentEntity() ) )
{
boolean isWall = false;
for ( Direction d : Direction.values() )
{
int nx = x + d.getX();
int ny = y + d.getY();
if ( nx >= 0 && nx < width && ny >= 0 && ny < height )
{
if ( roomContents[nx][ny] != null && !roomContents[nx][ny].isPassable( GeneratorPassability ) )
{
isWall = true;
break;
}
}
}
if ( isWall )
{
wallList.add( tile );
}
}
}
if ( wallList.size == 0 )
{
break;
}
int numTilesToPlace = f.getNumTilesToPlace( influence, wallList.size );
for ( int i = 0; i < numTilesToPlace; i++ )
{
Point pos = wallList.removeIndex( ran.nextInt( wallList.size ) );
roomContents[pos.x][pos.y] = f.getAsSymbol( roomContents[pos.x][pos.y] );
if ( wallList.size == 0 )
{
break;
}
}
}
}
// Do centre
{
Array<Feature> features = faction.features.get( FeaturePlacementType.CENTRE );
for ( Feature f : features )
{
if ( influence < f.minRange || influence > f.maxRange )
{
continue;
}
// Build list
Array<Point> centreList = new Array<Point>();
for ( Point tile : validList )
{
int x = tile.x;
int y = tile.y;
if ( roomContents[x][y].getTileData().canFeature
&& roomContents[x][y].isPassable( GeneratorPassability )
&& ( f.environmentData == null || !roomContents[x][y].hasEnvironmentEntity() ) )
{
boolean isWall = false;
for ( Direction d : Direction.values() )
{
int nx = x + d.getX();
int ny = y + d.getY();
if ( nx >= 0 && nx < width && ny >= 0 && ny < height )
{
if ( roomContents[nx][ny] != null && !roomContents[nx][ny].isPassable( GeneratorPassability ) )
{
isWall = true;
break;
}
}
}
if ( !isWall )
{
centreList.add( tile );
}
}
}
if ( centreList.size == 0 )
{
break;
}
int numTilesToPlace = f.getNumTilesToPlace( influence, centreList.size );
for ( int i = 0; i < numTilesToPlace; i++ )
{
Point pos = centreList.removeIndex( ran.nextInt( centreList.size ) );
roomContents[pos.x][pos.y] = f.getAsSymbol( roomContents[pos.x][pos.y] );
if ( centreList.size == 0 )
{
break;
}
}
}
}
// Do any
{
Array<Feature> features = faction.features.get( FeaturePlacementType.ANY );
for ( Feature f : features )
{
if ( influence < f.minRange || influence > f.maxRange )
{
continue;
}
Array<Point> anyList = new Array<Point>();
for ( Point tile : validList )
{
int x = tile.x;
int y = tile.y;
if ( roomContents[x][y].getTileData().canFeature
&& roomContents[x][y].isPassable( GeneratorPassability )
&& ( f.environmentData == null || !roomContents[x][y].hasEnvironmentEntity() ) )
{
anyList.add( tile );
}
}
if ( anyList.size == 0 )
{
break;
}
int numTilesToPlace = f.getNumTilesToPlace( influence, anyList.size );
for ( int i = 0; i < numTilesToPlace; i++ )
{
Point pos = anyList.removeIndex( ran.nextInt( anyList.size ) );
roomContents[pos.x][pos.y] = f.getAsSymbol( roomContents[pos.x][pos.y] );
if ( anyList.size == 0 )
{
break;
}
}
}
}
// Ensure connectivity
for ( int i = 0; i < doors.size; i++ )
{
RoomDoor door = doors.get( i );
for ( int ii = i+1; ii < doors.size; ii++ )
{
RoomDoor otherDoor = doors.get( ii );
AStarPathfind pathfind = new AStarPathfind( roomContents, door.pos.x, door.pos.y, otherDoor.pos.x, otherDoor.pos.y, Global.CanMoveDiagonal, false, 1, GeneratorPassability, null );
Array<Point> path = pathfind.getPath();
if (path == null)
{
print();
continue;
}
//if (path != null)
{
for ( Point point : path )
{
Symbol s = roomContents[point.x][point.y];
if ( dfp.corridorStyle != null && dfp.corridorStyle.width == 1 && dfp.corridorStyle.centralConstant != null && (roomData == null || !roomData.skipPlacingCorridor) )
{
s = dfp.corridorStyle.centralConstant.getAsSymbol( s, dfp );
}
else if ( !s.isPassable( GeneratorPassability ) )
{
s.tileData = roomCopy[point.x][point.y].tileData;
}
if ( s.hasEnvironmentEntity()
&& !s.environmentData.get( "Type", "" ).equals( "Door" )
&& !s.getEnvironmentEntityPassable( GeneratorPassability ) )
{
s.environmentData = roomCopy[point.x][point.y].environmentData;
}
roomContents[point.x][point.y] = s;
}
Global.PointPool.freeAll( path );
}
}
}
// Do spawn
{
// get valid spawn tiles
Array<Point> spawnList = new Array<Point>();
Array<Point> blockedList = new Array<Point>( );
for ( Point tile : fullList )
{
int x = tile.x;
int y = tile.y;
if ( roomContents[x][y].getTileData().canSpawn &&
roomContents[x][y].isPassable( GeneratorPassability ) &&
!roomContents[x][y].hasGameEntity())
{
if ( roomContents[x][y].getEnvironmentEntityPassable( GeneratorPassability ) )
{
spawnList.add( tile );
}
else
{
blockedList.add( tile );
}
}
}
if (spawnMiniBoss)
{
if ( spawnList.size == 0 )
{
if ( blockedList.size > 0 )
{
Point p = blockedList.removeIndex( 0 );
roomContents[p.x][p.y].environmentData = roomCopy[p.x][p.y].environmentData;
spawnList.add( p );
}
}
if ( spawnList.size > 0 )
{
String entityName = null;
if ( faction.minibosses.size > 0 )
{
entityName = faction.minibosses.get( ran.nextInt( faction.minibosses.size ) );
}
else
{
entityName = faction.creatures.get( faction.creatures.size - 1 ).entityName;
}
Point pos = spawnList.removeIndex( ran.nextInt( spawnList.size ) );
roomContents[pos.x][pos.y] = roomContents[pos.x][pos.y].copy();
roomContents[pos.x][pos.y].entityData = entityName;
spawnMiniBoss = false;
}
}
double difficulty = spawnList.size > 0 ? Math.log( spawnList.size ) : 0;
difficulty /= 2;
difficulty = Math.pow( difficulty, 2 );
difficulty = Math.max( 1, difficulty );
// place mobs
Array<Creature> creatures = faction.getCreatures( ran, (float) difficulty, influence );
for ( Creature creature : creatures )
{
if ( spawnList.size == 0 )
{
if ( blockedList.size == 0 )
{
break;
}
else
{
Point p = blockedList.removeIndex( 0 );
roomContents[p.x][p.y].environmentData = roomCopy[p.x][p.y].environmentData;
spawnList.add( p );
}
}
Point pos = spawnList.removeIndex( ran.nextInt( spawnList.size ) );
roomContents[pos.x][pos.y] = roomContents[pos.x][pos.y].copy();
roomContents[pos.x][pos.y].entityData = creature.entityName;
roomContents[pos.x][pos.y].character = 'E';
}
}
print();
Global.PointPool.freeAll( fullList );
return !spawnMiniBoss;
}
// ----------------------------------------------------------------------
private void addDoor( int pos, int space, Direction dir, Random ran, DungeonFileParser dfp )
{
if ( space >= dfp.corridorStyle.width )
{
int offset = space > dfp.corridorStyle.width ? ran.nextInt( space - dfp.corridorStyle.width ) : 0;
if ( dir == Direction.WEST )
{
doors.add( new RoomDoor( Direction.WEST, new Point( 0, pos + offset ) ) );
}
else if ( dir == Direction.EAST )
{
doors.add( new RoomDoor( Direction.EAST, new Point( width - 1, pos + offset ) ) );
}
else if ( dir == Direction.NORTH )
{
doors.add( new RoomDoor( Direction.NORTH, new Point( pos + offset, 0 ) ) );
}
else if ( dir == Direction.SOUTH )
{
doors.add( new RoomDoor( Direction.SOUTH, new Point( pos + offset, height - 1 ) ) );
}
}
}
// ----------------------------------------------------------------------
private void processSide( Direction dir, Random ran, DungeonFileParser dfp )
{
int range = ( dir == Direction.WEST || dir == Direction.EAST ) ? height : width;
int blockStart = -1;
for ( int pos = 1; pos < range - 1; pos++ )
{
int x = 0;
int y = 0;
if ( dir == Direction.WEST )
{
x = 0;
y = pos;
}
else if ( dir == Direction.EAST )
{
x = width - 1;
y = pos;
}
else if ( dir == Direction.NORTH )
{
x = pos;
y = 0;
}
else
{
x = pos;
y = height - 1;
}
if ( blockStart >= 0 )
{
if ( !roomContents[x][y].isPassable( GeneratorPassability ) )
{
addDoor( blockStart, pos - blockStart, dir, ran, dfp );
blockStart = -1;
}
}
else
{
if ( roomContents[x][y].isPassable( GeneratorPassability ) )
{
blockStart = pos;
}
}
}
if ( blockStart >= 0 )
{
int pos = range - 1;
addDoor( blockStart, pos - blockStart, dir, ran, dfp );
}
}
// ----------------------------------------------------------------------
public void findDoors( Random ran, DungeonFileParser dfp )
{
// Sides
// 1
// 0 2
// 3
// Side 0
processSide( Direction.WEST, ran, dfp );
// Side 2
processSide( Direction.EAST, ran, dfp );
// Side 1
// Side 1
processSide( Direction.NORTH, ran, dfp );
// Side 3
processSide( Direction.SOUTH, ran, dfp );
}
// ----------------------------------------------------------------------
private class FeatureTile implements Comparable<FeatureTile>
{
public Point pos;
public int dist;
public FeatureTile( Point pos, Array<RoomDoor> doors )
{
this.pos = pos;
int d = 0;
for ( RoomDoor door : doors )
{
d += Math.abs( pos.x - door.pos.x ) + Math.abs( pos.y - door.pos.y );
}
d /= doors.size;
dist = d;
}
@Override
public int compareTo( FeatureTile o )
{
return ( (Integer) dist ).compareTo( o.dist );
}
}
// ----------------------------------------------------------------------
public static final class RoomDoor
{
public Direction side;
public Point pos;
public RoomDoor()
{
}
public RoomDoor( Direction side, Point pos )
{
this.side = side;
this.pos = pos;
}
}
}