package Roguelike.DungeonGeneration; import java.io.IOException; import java.util.HashMap; import java.util.Map; import java.util.Random; import net.objecthunter.exp4j.Expression; import net.objecthunter.exp4j.ExpressionBuilder; import Roguelike.AssetManager; import Roguelike.Global; import Roguelike.DungeonGeneration.RoomGenerators.AbstractRoomGenerator; import Roguelike.Sound.RepeatingSoundEffect; import Roguelike.Sprite.Sprite; import com.badlogic.gdx.Gdx; import com.badlogic.gdx.files.FileHandle; import com.badlogic.gdx.graphics.Color; import com.badlogic.gdx.utils.Array; import com.badlogic.gdx.utils.XmlReader; import com.badlogic.gdx.utils.XmlReader.Element; import exp4j.Helpers.EquationHelper; public class DungeonFileParser { // ---------------------------------------------------------------------- public static final class CorridorStyle { public enum PathStyle { STRAIGHT, WANDERING } public PathStyle pathStyle = PathStyle.STRAIGHT; public int width = 1; public CorridorFeature centralConstant; public CorridorFeature centralRecurring; public CorridorFeature sideRecurring; public void parse( Element xml ) { pathStyle = PathStyle.valueOf( xml.get( "PathStyle", "Straight" ).toUpperCase() ); width = xml.getInt( "Width", 1 ); Element centralConstantElement = xml.getChildByName( "CentralConstant" ); if ( centralConstantElement != null ) { centralConstant = CorridorFeature.load( centralConstantElement ); } Element centralRecurringElement = xml.getChildByName( "CentralRecurring" ); if ( centralRecurringElement != null ) { centralRecurring = CorridorFeature.load( centralRecurringElement ); } Element sideRecurringElement = xml.getChildByName( "SideRecurring" ); if ( sideRecurringElement != null ) { sideRecurring = CorridorFeature.load( sideRecurringElement ); } } } // ---------------------------------------------------------------------- public static final class CorridorFeature { public enum PlacementMode { BOTH, TOP, BOTTOM, ALTERNATE } public Symbol symbol; public int interval; public PlacementMode placementMode; public static CorridorFeature load( Element xml ) { CorridorFeature feature = new CorridorFeature(); feature.interval = xml.getInt( "Interval", 0 ); feature.placementMode = PlacementMode.valueOf( xml.get( "PlacementMode", "Both" ).toUpperCase() ); feature.symbol = Symbol.parse( xml ); return feature; } public Symbol getAsSymbol( Symbol current, DungeonFileParser dfp ) { symbol.resolveExtends( dfp.sharedSymbolMap ); Symbol nsymbol = current.copy(); nsymbol.character = 'C'; nsymbol.tileData = symbol.tileData != null ? symbol.tileData : current.tileData; nsymbol.environmentData = symbol.environmentData != null ? symbol.environmentData : current.environmentData; return nsymbol; } } // ---------------------------------------------------------------------- public static final class RoomGenerator { public AbstractRoomGenerator generator; public int weight; } // ---------------------------------------------------------------------- public static final class Faction { public String name; public int weight; public Faction( String name, int weight ) { this.name = name; this.weight = weight; } } // ---------------------------------------------------------------------- public static final class DFPRoom { // ---------------------------------------------------------------------- public enum Placement { NORTH, SOUTH, EAST, WEST, CENTRE } public Placement placement; public String spawnEquation = "1"; public String width; public String height; public HashMap<Character, Symbol> symbolMap = new HashMap<Character, Symbol>(); public char[][] roomDef; public String faction; public boolean addFactionFeatures = true; public AbstractRoomGenerator generator; public String placementHint; public boolean lockRotation = false; public boolean skipPlacingCorridor = false; private boolean symbolsResolved = false; public static DFPRoom parse( Element xml ) { DFPRoom room = new DFPRoom(); room.spawnEquation = xml.getAttribute( "Condition", room.spawnEquation ).toLowerCase(); room.spawnEquation = xml.getAttribute( "Count", room.spawnEquation ).toLowerCase(); room.placementHint = xml.get( "PlacementHint", null ); room.faction = xml.get( "Faction", null ); room.placement = Placement.valueOf( xml.get( "Placement", "Centre" ).toUpperCase() ); room.lockRotation = xml.getBoolean( "LockRotation", false ); room.skipPlacingCorridor = xml.getBoolean( "SkipPlacingCorridor", false ); if (room.skipPlacingCorridor) { int i = 0; } room.addFactionFeatures = xml.getBoolean( "AddFeatures", true ); Element rowsElement = xml.getChildByName( "Rows" ); if ( rowsElement != null ) { if ( rowsElement.getChildCount() > 0 ) { // Rows defined here int width = 0; int height = rowsElement.getChildCount(); for ( int i = 0; i < height; i++ ) { if ( rowsElement.getChild( i ).getText().length() > width ) { width = rowsElement.getChild( i ).getText().length(); } } room.height = "" + height; room.width = "" + width; room.roomDef = new char[width][height]; for ( int x = 0; x < width; x++ ) { for ( int y = 0; y < height; y++ ) { room.roomDef[x][y] = rowsElement.getChild( y ).getText().charAt( x ); } } } else { // Rows in seperate csv file String fileName = rowsElement.getText(); FileHandle handle = Gdx.files.internal( fileName + ".csv" ); String content = handle.readString(); String[] lines = content.split( System.getProperty( "line.separator" ) ); int height = 0; int width = lines.length; String[][] rows = new String[lines.length][]; for ( int i = 0; i < lines.length; i++ ) { rows[i] = lines[i].split( " " ); height = rows[i].length; } room.width = "" + width; room.height = "" + height; room.roomDef = new char[width][height]; for ( int x = 0; x < width; x++ ) { for ( int y = 0; y < height; y++ ) { room.roomDef[x][y] = rows[x][y].charAt( 0 ); } } } } else { Element generatorElement = xml.getChildByName( "Generator" ); if (generatorElement != null) { generatorElement = generatorElement.getChild( 0 ); room.generator = AbstractRoomGenerator.load( generatorElement ); } room.width = xml.get( "Width", "rnd(6)+4" ); room.height = xml.get( "Height", "rnd(6)+4" ); } Element symbolsElement = xml.getChildByName( "Symbols" ); if ( symbolsElement != null ) { for ( int i = 0; i < symbolsElement.getChildCount(); i++ ) { Symbol symbol = Symbol.parse( symbolsElement.getChild( i ) ); room.symbolMap.put( symbol.character, symbol ); } } return room; } public void resolveSymbols(HashMap<Character, Symbol> sharedSymbolMap) { for (Map.Entry<Character, Symbol> pair : sharedSymbolMap.entrySet()) { if ( !symbolMap.containsKey( pair.getKey() ) ) { symbolMap.put( pair.getKey(), pair.getValue().copy() ); } } for (Symbol s : symbolMap.values()) { s.resolveExtends( symbolMap ); } symbolsResolved = true; } public DFPRoom copy() { DFPRoom room = new DFPRoom(); room.placement = placement; room.spawnEquation = spawnEquation; room.width = width; room.height = height; room.roomDef = roomDef; room.faction = faction; room.addFactionFeatures = addFactionFeatures; room.generator = generator; room.placementHint = placementHint; room.lockRotation = lockRotation; room.skipPlacingCorridor = skipPlacingCorridor; room.symbolsResolved = symbolsResolved; for ( Character key : symbolMap.keySet() ) { room.symbolMap.put( key, symbolMap.get( key ).copy() ); } return room; } public Symbol getSymbol( char c ) { Symbol s = symbolMap.get( c ); if ( s == null ) { throw new RuntimeException( "Attempted to use undefined symbol: " + c ); } return s; } public int processCondition( int depth, Random ran, boolean isBoss ) { if ( Global.isNumber( spawnEquation ) ) { return Integer.parseInt( spawnEquation ); } else { ExpressionBuilder expB = EquationHelper.createEquationBuilder( spawnEquation, ran ); expB.variable( "depth" ); expB.variable( "boss" ); Expression exp = EquationHelper.tryBuild( expB ); if ( exp == null ) { return 0; } exp.setVariable( "depth", depth ); exp.setVariable( "boss", isBoss ? 1 : 0 ); int val = (int) exp.evaluate(); return val; } } public void fillRoom( Room room, Random ran, DungeonFileParser dfp ) { if (!symbolsResolved) { resolveSymbols( dfp.sharedSymbolMap ); } room.roomData = this; room.width = EquationHelper.evaluate( width, Global.Statistic.emptyMap, ran ); room.height = EquationHelper.evaluate( height, Global.Statistic.emptyMap, ran ); room.roomContents = new Symbol[room.width][room.height]; room.faction = faction; if ( generator != null ) { Symbol floor = getSymbol( '.' ); Symbol wall = getSymbol( '#' ); floor.resolveExtends( dfp.sharedSymbolMap ); wall.resolveExtends( dfp.sharedSymbolMap ); room.generateRoomContents( ran, dfp, floor, wall, generator ); } else if ( roomDef == null ) { Symbol floor = getSymbol( '.' ); Symbol wall = getSymbol( '#' ); floor.resolveExtends( dfp.sharedSymbolMap ); wall.resolveExtends( dfp.sharedSymbolMap ); AbstractRoomGenerator generator = dfp.getRoomGenerator( ran ); room.generateRoomContents( ran, dfp, floor, wall, generator ); } else { for ( int x = 0; x < room.width; x++ ) { for ( int y = 0; y < room.height; y++ ) { room.roomContents[x][y] = getSymbol( roomDef[x][y] ); } } } } } // ---------------------------------------------------------------------- public String generator; // ---------------------------------------------------------------------- public HashMap<Character, Symbol> sharedSymbolMap = new HashMap<Character, Symbol>(); // ---------------------------------------------------------------------- public Array<DFPRoom> rooms = new Array<DFPRoom>(); // ---------------------------------------------------------------------- public Array<Faction> majorFactions = new Array<Faction>(); // ---------------------------------------------------------------------- public Array<Faction> minorFactions = new Array<Faction>(); // ---------------------------------------------------------------------- public Color ambient; // ---------------------------------------------------------------------- public Sprite background; // ---------------------------------------------------------------------- public char[][] roomDef; // ---------------------------------------------------------------------- public boolean affectedByDayNight = false; // ---------------------------------------------------------------------- public String BGM; // ---------------------------------------------------------------------- public Array<RepeatingSoundEffect> ambientSounds = new Array<RepeatingSoundEffect>(); // ---------------------------------------------------------------------- public Array<RoomGenerator> roomGenerators = new Array<RoomGenerator>(); // ---------------------------------------------------------------------- public CorridorStyle corridorStyle = new CorridorStyle(); // ---------------------------------------------------------------------- public RoomGenerator preprocessor; // ---------------------------------------------------------------------- public int minWidth; public int minHeight; // ---------------------------------------------------------------------- public boolean visionRestricted = true; // ---------------------------------------------------------------------- public HashMap<String, DFPRoom[]> entranceRooms = new HashMap<String, DFPRoom[]>( ); // ---------------------------------------------------------------------- public Symbol getSymbol( char c ) { Symbol s = sharedSymbolMap.get( c ); if ( s == null ) { System.out.println( "Failed to find symbol for character '" + c + "'! Falling back to using '.'" ); s = sharedSymbolMap.get( '.' ); } return s; } // ---------------------------------------------------------------------- public void addDefaultSymbols() { for (int i = 0; i < 10; i++) { char c = (""+i).charAt( 0 ); if (!sharedSymbolMap.containsKey( c )) { Symbol symbol = new Symbol(); symbol.character = c; symbol.entityData = ""+c; symbol.extendsSymbol = '.'; sharedSymbolMap.put( c, symbol ); } } if (!sharedSymbolMap.containsKey( 'B' )) { Symbol symbol = new Symbol(); symbol.character = 'B'; symbol.entityData = "Boss"; symbol.extendsSymbol = '.'; sharedSymbolMap.put( 'B', symbol ); } } // ---------------------------------------------------------------------- public Array<DFPRoom> getRooms( int depth, Random ran, boolean isBoss, FactionParser majorFaction, Array<FactionParser> minorFactions ) { Array<DFPRoom> outRooms = new Array<DFPRoom>(); for ( DFPRoom room : rooms ) { int count = room.processCondition( depth, ran, isBoss ); for ( int i = 0; i < count; i++ ) { outRooms.add( room.copy() ); } } if (majorFaction != null) { for ( DFPRoom room : majorFaction.rooms ) { int count = room.processCondition( depth, ran, isBoss ); for ( int i = 0; i < count; i++ ) { DFPRoom cpy = room.copy(); cpy.faction = majorFaction.name; outRooms.add( cpy ); } } } for (FactionParser faction : minorFactions) { if (ran.nextInt( 3 ) == 0) { Array<DFPRoom> validRooms = new Array<DFPRoom>( ); for (DFPRoom room : faction.rooms) { int count = room.processCondition( depth, ran, false ); for ( int i = 0; i < count; i++ ) { DFPRoom cpy = room.copy(); cpy.faction = faction.name; validRooms.add( cpy ); } } if (validRooms.size > 0) { outRooms.add( validRooms.get( ran.nextInt( validRooms.size ) ) ); } } } return outRooms; } // ---------------------------------------------------------------------- public String getMajorFaction( Random ran ) { int totalWeight = 0; for ( Faction fac : majorFactions ) { totalWeight += fac.weight; } int ranVal = ran.nextInt( totalWeight ); int currentWeight = 0; for ( Faction fac : majorFactions ) { currentWeight += fac.weight; if ( currentWeight >= ranVal ) { return fac.name; } } return null; } // ---------------------------------------------------------------------- public String getMinorFaction( Random ran ) { int totalWeight = 0; for ( Faction fac : minorFactions ) { totalWeight += fac.weight; } int ranVal = ran.nextInt( totalWeight ); int currentWeight = 0; for ( Faction fac : minorFactions ) { currentWeight += fac.weight; if ( currentWeight >= ranVal ) { return fac.name; } } return null; } // ---------------------------------------------------------------------- public AbstractRoomGenerator getRoomGenerator( Random ran ) { int totalWeight = 0; for ( RoomGenerator rg : roomGenerators ) { totalWeight += rg.weight; } int target = ran.nextInt( totalWeight ); int current = 0; for ( RoomGenerator rg : roomGenerators ) { current += rg.weight; if ( current >= target ) { return rg.generator; } } return null; } // ---------------------------------------------------------------------- private void internalLoad( String name ) { XmlReader xml = new XmlReader(); Element xmlElement = null; try { xmlElement = xml.parse( Gdx.files.internal( "Levels/" + name + ".xml" ) ); } catch ( IOException e ) { e.printStackTrace(); } generator = xmlElement.get( "Generator", "RecursiveDock" ); Element roomGenElement = xmlElement.getChildByName( "RoomGenerators" ); if ( roomGenElement != null ) { for ( int i = 0; i < roomGenElement.getChildCount(); i++ ) { Element roomGen = roomGenElement.getChild( i ); RoomGenerator gen = new RoomGenerator(); gen.generator = AbstractRoomGenerator.load( roomGen ); gen.weight = roomGen.getInt( "Weight" ); roomGenerators.add( gen ); } } Element backgroundElement = xmlElement.getChildByName( "Background" ); if ( backgroundElement != null ) { background = AssetManager.loadSprite( backgroundElement ); } Element corridorElement = xmlElement.getChildByName( "CorridorStyle" ); if ( corridorElement != null ) { corridorStyle.parse( corridorElement ); } Element preprocessorElement = xmlElement.getChildByName( "Preprocessor" ); if ( preprocessorElement != null ) { preprocessor = new RoomGenerator(); preprocessor.generator = AbstractRoomGenerator.load( preprocessorElement.getChild( 0 ) ); } Element factionsElement = xmlElement.getChildByName( "Factions" ); if ( factionsElement != null ) { Element majorFacElement = factionsElement.getChildByName( "Major" ); for ( int i = 0; i < majorFacElement.getChildCount(); i++ ) { Element facElement = majorFacElement.getChild( i ); String facname = facElement.getName(); int weight = Integer.parseInt( facElement.getText() ); majorFactions.add( new Faction( facname, weight ) ); } Element minorFacElement = factionsElement.getChildByName( "Minor" ); for ( int i = 0; i < minorFacElement.getChildCount(); i++ ) { Element facElement = minorFacElement.getChild( i ); String facname = facElement.getName(); int weight = Integer.parseInt( facElement.getText() ); minorFactions.add( new Faction( facname, weight ) ); } } Element symbolsElement = xmlElement.getChildByName( "Symbols" ); for ( int i = 0; i < symbolsElement.getChildCount(); i++ ) { Symbol symbol = Symbol.parse( symbolsElement.getChild( i ) ); sharedSymbolMap.put( symbol.character, symbol ); } addDefaultSymbols(); for (Element el : xmlElement.getChildrenByName( "Entrance" )) { String key = el.getAttribute( "Key", "all" ).toLowerCase(); DFPRoom prevRoom = DFPRoom.parse( el.getChildByName( "Prev" ) ); DFPRoom thisRoom = DFPRoom.parse( el.getChildByName( "This" ) ); entranceRooms.put( key, new DFPRoom[]{ prevRoom, thisRoom } ); } Element roomsElement = xmlElement.getChildByName( "Rooms" ); if ( roomsElement != null ) { for ( int i = 0; i < roomsElement.getChildCount(); i++ ) { DFPRoom room = DFPRoom.parse( roomsElement.getChild( i ) ); rooms.add( room ); } } minWidth = xmlElement.getInt( "MinWidth", 10 ); minHeight = xmlElement.getInt( "MinHeight", 10 ); Element ae = xmlElement.getChildByName( "Ambient" ); ambient = new Color( ae.getFloat( "Red", 1 ), ae.getFloat( "Blue", 1 ), ae.getFloat( "Green", 1 ), ae.getFloat( "Alpha", 1 ) ); affectedByDayNight = ae.getBoolean( "AffectedByDayNight", false ); Element soundElement = xmlElement.getChildByName( "Sound" ); BGM = soundElement.get( "BGM" ); Element ambientElement = soundElement.getChildByName( "Ambient" ); if (ambientElement != null) { for ( Element ambientSound : ambientElement.getChildrenByName( "Sound" ) ) { ambientSounds.add( RepeatingSoundEffect.parse( ambientSound ) ); } } visionRestricted = xmlElement.getBoolean( "VisionRestricted", true ); Element rowsElement = xmlElement.getChildByName( "Rows" ); if ( rowsElement != null ) { if ( rowsElement.getChildCount() > 0 ) { // Rows defined here int width = 0; int height = rowsElement.getChildCount(); for ( int i = 0; i < height; i++ ) { if ( rowsElement.getChild( i ).getText().length() > width ) { width = rowsElement.getChild( i ).getText().length(); } } roomDef = new char[width][height]; for ( int x = 0; x < width; x++ ) { for ( int y = 0; y < height; y++ ) { roomDef[x][y] = rowsElement.getChild( y ).getText().charAt( x ); } } } else { // Rows in seperate csv file String fileName = rowsElement.getText(); FileHandle handle = Gdx.files.internal( "Levels/" + fileName + ".txt" ); String content = handle.readString(); content = content.replace( "\u0000", "" ); content = content.replaceAll("[^\\p{ASCII}]", ""); String[] lines = content.split( "\n" ); int height = lines.length; int width = lines[0].length(); roomDef = new char[width][height]; for ( int x = 0; x < width; x++ ) { for ( int y = 0; y < height; y++ ) { roomDef[x][y] = lines[height-y-1].charAt( x ); } } } } } // ---------------------------------------------------------------------- public static DungeonFileParser load( String name ) { DungeonFileParser dfp = new DungeonFileParser(); dfp.internalLoad( name ); return dfp; } }