From c6105afc801ad68fcae88a0f1a30fa53da0a356e Mon Sep 17 00:00:00 2001 From: Regalis Date: Mon, 12 Sep 2016 20:18:43 +0300 Subject: [PATCH] A separate class for parameters used by the level generator, different "level types" with configurable parameters --- Subsurface/Barotrauma.csproj | 5 + .../Content/Map/LevelGenerationParameters.xml | 72 +++++ Subsurface/Source/ContentPackage.cs | 12 +- Subsurface/Source/DebugConsole.cs | 4 - Subsurface/Source/GameMain.cs | 1 + Subsurface/Source/Map/Levels/CaveGenerator.cs | 14 +- Subsurface/Source/Map/Levels/Level.cs | 282 +++++++++--------- .../Map/Levels/LevelGenerationParams.cs | 216 ++++++++++++++ Subsurface/Source/Map/Levels/WrappingWall.cs | 20 +- Subsurface/Source/Utils/ToolBox.cs | 48 ++- 10 files changed, 496 insertions(+), 178 deletions(-) create mode 100644 Subsurface/Content/Map/LevelGenerationParameters.xml create mode 100644 Subsurface/Source/Map/Levels/LevelGenerationParams.cs diff --git a/Subsurface/Barotrauma.csproj b/Subsurface/Barotrauma.csproj index 6d0b466df..a6d01c714 100644 --- a/Subsurface/Barotrauma.csproj +++ b/Subsurface/Barotrauma.csproj @@ -136,6 +136,7 @@ + @@ -768,6 +769,10 @@ PreserveNewest + + PreserveNewest + Designer + PreserveNewest diff --git a/Subsurface/Content/Map/LevelGenerationParameters.xml b/Subsurface/Content/Map/LevelGenerationParameters.xml new file mode 100644 index 000000000..f593fbfc1 --- /dev/null +++ b/Subsurface/Content/Map/LevelGenerationParameters.xml @@ -0,0 +1,72 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/Subsurface/Source/ContentPackage.cs b/Subsurface/Source/ContentPackage.cs index 52a3397de..1e6b65557 100644 --- a/Subsurface/Source/ContentPackage.cs +++ b/Subsurface/Source/ContentPackage.cs @@ -10,7 +10,17 @@ namespace Barotrauma { public enum ContentType { - None, Jobs, Item, Character, Structure, Executable, LocationTypes, RandomEvents, Missions, BackgroundCreaturePrefabs, BackgroundSpritePrefabs + None, + Jobs, + Item, + Character, + Structure, + Executable, + LocationTypes, + LevelGenerationPresets, + RandomEvents, + Missions, + BackgroundCreaturePrefabs, BackgroundSpritePrefabs } public class ContentPackage diff --git a/Subsurface/Source/DebugConsole.cs b/Subsurface/Source/DebugConsole.cs index 3d33e997b..cbd52d02f 100644 --- a/Subsurface/Source/DebugConsole.cs +++ b/Subsurface/Source/DebugConsole.cs @@ -493,10 +493,6 @@ namespace Barotrauma case "fire": if (GameMain.Client == null) Hull.EditFire = !Hull.EditFire; - break; - case "generatelevel": - GameMain.Level = new Level("asdf", 50.0f, 500,500, 50); - GameMain.Level.Generate(); break; case "fixitems": foreach (Item it in Item.ItemList) diff --git a/Subsurface/Source/GameMain.cs b/Subsurface/Source/GameMain.cs index 795cf1dbf..7de66445f 100644 --- a/Subsurface/Source/GameMain.cs +++ b/Subsurface/Source/GameMain.cs @@ -206,6 +206,7 @@ namespace Barotrauma Mission.Init(); MapEntityPrefab.Init(); + LevelGenerationParams.LoadPresets(); TitleScreen.LoadState = 10.0f; yield return CoroutineStatus.Running; diff --git a/Subsurface/Source/Map/Levels/CaveGenerator.cs b/Subsurface/Source/Map/Levels/CaveGenerator.cs index 0602aa5e6..e1836f09d 100644 --- a/Subsurface/Source/Map/Levels/CaveGenerator.cs +++ b/Subsurface/Source/Map/Levels/CaveGenerator.cs @@ -189,16 +189,18 @@ namespace Barotrauma { Site site = (i == 0) ? ge.site1 : ge.site2; - VoronoiCell cell = cellGrid[ - (int)Math.Floor((site.coord.x-borders.X) / gridCellSize), - (int)Math.Floor((site.coord.y-borders.Y) / gridCellSize)].Find(c => c.site == site); + int x = (int)(Math.Floor((site.coord.x-borders.X) / gridCellSize)); + int y = (int)(Math.Floor((site.coord.y-borders.Y) / gridCellSize)); + + x = MathHelper.Clamp(x, 0, cellGrid.GetLength(0)-1); + y = MathHelper.Clamp(y, 0, cellGrid.GetLength(1)-1); + + VoronoiCell cell = cellGrid[x,y].Find(c => c.site == site); if (cell == null) { cell = new VoronoiCell(site); - cellGrid[ - (int)Math.Floor((cell.Center.X-borders.X) / gridCellSize), - (int)Math.Floor((cell.Center.Y - borders.Y) / gridCellSize)].Add(cell); + cellGrid[x, y].Add(cell); cells.Add(cell); } diff --git a/Subsurface/Source/Map/Levels/Level.cs b/Subsurface/Source/Map/Levels/Level.cs index d60196be0..fb13abd02 100644 --- a/Subsurface/Source/Map/Levels/Level.cs +++ b/Subsurface/Source/Map/Levels/Level.cs @@ -50,9 +50,7 @@ namespace Barotrauma public const float ExitDistance = 6000.0f; private string seed; - - private int siteInterval; - + public const int GridCellSize = 2000; private List[,] cellGrid; @@ -77,6 +75,8 @@ namespace Barotrauma private Color backgroundColor; + private LevelGenerationParams generationParams; + public Vector2 StartPosition { get { return startPosition; } @@ -124,24 +124,22 @@ namespace Barotrauma get { return backgroundColor; } } - public Level(string seed, float difficulty, int width, int height, int siteInterval) + public Level(string seed, float difficulty, LevelGenerationParams generationParams) { this.seed = seed; - - this.siteInterval = siteInterval; - + this.Difficulty = difficulty; - borders = new Rectangle(0, 0, width, height); + this.generationParams = generationParams; + + borders = new Rectangle(0, 0, (int)generationParams.Width, (int)generationParams.Height); } public static Level CreateRandom(LocationConnection locationConnection) { string seed = locationConnection.Locations[0].Name + locationConnection.Locations[1].Name; - - Rand.SetSyncedSeed(ToolBox.StringToInt(seed)); - - return new Level(seed, locationConnection.Difficulty, Rand.Range(80000, 120000, false), Rand.Range(40000, 60000, false), 2000); + + return new Level(seed, locationConnection.Difficulty, LevelGenerationParams.GetRandom(seed)); } public static Level CreateRandom(string seed = "") @@ -153,10 +151,10 @@ namespace Barotrauma Rand.SetSyncedSeed(ToolBox.StringToInt(seed)); - return new Level(seed, Rand.Range(30.0f, 80.0f, false), Rand.Range(80000, 120000, false), Rand.Range(40000, 60000, false), 2000); + return new Level(seed, Rand.Range(30.0f, 80.0f, false), LevelGenerationParams.GetRandom(seed)); } - public void Generate(bool mirror=false) + public void Generate(bool mirror = false) { Stopwatch sw = new Stopwatch(); sw.Start(); @@ -175,47 +173,51 @@ namespace Barotrauma bodies = new List(); Rand.SetSyncedSeed(ToolBox.StringToInt(seed)); - - float brightness = Rand.Range(1.0f, 1.3f, false); - backgroundColor = Color.Lerp(new Color(11, 18, 26), new Color(50, 46, 20), Rand.Range(0.0f, 1.0f, false)) * brightness; - backgroundColor = new Color(backgroundColor, 1.0f); - + + backgroundColor = generationParams.BackgroundColor; float avgValue = (backgroundColor.R + backgroundColor.G + backgroundColor.G) / 3; GameMain.LightManager.AmbientLight = new Color(backgroundColor * (60.0f / avgValue), 1.0f); float minWidth = Submarine.MainSub == null ? 0.0f : Math.Max(Submarine.MainSub.Borders.Width, Submarine.MainSub.Borders.Height); minWidth = Math.Max(minWidth, 6500.0f); - startPosition = new Vector2(minWidth * 2, Rand.Range(minWidth * 2, borders.Height - minWidth * 2, false)); - endPosition = new Vector2(borders.Width - minWidth * 2, Rand.Range(minWidth * 2, borders.Height - minWidth * 2, false)); + startPosition = new Vector2( + Rand.Range(minWidth * 2, minWidth * 4, false), + Rand.Range(minWidth * 2, borders.Height - minWidth * 2, false)); + + endPosition = new Vector2( + borders.Width - Rand.Range(minWidth * 2, minWidth * 4, false), + Rand.Range(minWidth * 2, borders.Height - minWidth * 2, false)); List pathNodes = new List(); Rectangle pathBorders = borders;// new Rectangle((int)minWidth, (int)minWidth, borders.Width - (int)minWidth * 2, borders.Height - (int)minWidth); pathBorders.Inflate(-minWidth*2, -minWidth*2); - pathNodes.Add(new Vector2(startPosition.X, borders.Height)); pathNodes.Add(startPosition); - for (float x = startPosition.X; x < endPosition.X; x += Rand.Range(5000.0f, 10000.0f, false)) + for (float x = startPosition.X; + x < endPosition.X; + x += Rand.Range(generationParams.MainPathNodeIntervalRange.X, generationParams.MainPathNodeIntervalRange.Y, false)) { pathNodes.Add(new Vector2(x, Rand.Range(pathBorders.Y, pathBorders.Bottom, false))); } pathNodes.Add(endPosition); pathNodes.Add(new Vector2(endPosition.X, borders.Height)); - - int smallTunnelCount = 5; - + List> smallTunnels = new List>(); - for (int i = 0; i < smallTunnelCount; i++) + for (int i = 0; i < generationParams.SmallTunnelCount; i++) { var tunnelStartPos = pathNodes[Rand.Range(2, pathNodes.Count - 2, false)]; tunnelStartPos.X = MathHelper.Clamp(tunnelStartPos.X, pathBorders.X, pathBorders.Right); - float tunnelLength = Rand.Range(5000.0f, 10000.0f, false); + float tunnelLength = Rand.Range( + generationParams.SmallTunnelLengthRange.X, + generationParams.SmallTunnelLengthRange.Y, + false); var tunnelNodes = MathUtils.GenerateJaggedLine( tunnelStartPos, @@ -231,21 +233,23 @@ namespace Barotrauma if (tunnel.Any()) smallTunnels.Add(tunnel); } - - - - float siteVariance = siteInterval * 0.4f; - for (int x = siteInterval / 2; x < borders.Width; x += siteInterval) + + Vector2 siteInterval = generationParams.VoronoiSiteInterval; + Vector2 siteVariance = generationParams.VoronoiSiteVariance; + for (float x = siteInterval.X / 2; x < borders.Width; x += siteInterval.X) { - for (int y = siteInterval / 2; y < borders.Height; y += siteInterval) + for (float y = siteInterval.Y / 2; y < borders.Height; y += siteInterval.Y) { - Vector2 site = new Vector2(x, y) + Rand.Vector(siteVariance, false); + Vector2 site = new Vector2( + x + Rand.Range(-siteVariance.X, siteVariance.X, false), + y + Rand.Range(-siteVariance.Y, siteVariance.Y, false)); - if (smallTunnels.Any(t => t.Any(node => Vector2.Distance(node, site) < siteInterval))) + if (smallTunnels.Any(t => t.Any(node => Vector2.Distance(node, site) < siteInterval.Length()))) { - if (x < borders.Width - siteInterval) sites.Add(new Vector2(x, y) + Vector2.UnitX * siteInterval * 0.5f); - if (y < borders.Height - siteInterval) sites.Add(new Vector2(x, y) + Vector2.UnitY * siteInterval * 0.5f); - if (x < borders.Width - siteInterval && y < borders.Height - siteInterval) sites.Add(new Vector2(x, y) + Vector2.One * siteInterval * 0.5f); + //add some more sites around the small tunnels to generate more small voronoi cells + if (x < borders.Width - siteInterval.X) sites.Add(new Vector2(x, y) + Vector2.UnitX * siteInterval * 0.5f); + if (y < borders.Height - siteInterval.Y) sites.Add(new Vector2(x, y) + Vector2.UnitY * siteInterval * 0.5f); + if (x < borders.Width - siteInterval.X && y < borders.Height - siteInterval.Y) sites.Add(new Vector2(x, y) + Vector2.One * siteInterval * 0.5f); } if (mirror) site.X = borders.Width - site.X; @@ -259,7 +263,6 @@ namespace Barotrauma List graphEdges = voronoi.MakeVoronoiGraph(sites, borders.Width, borders.Height); - Debug.WriteLine("MakeVoronoiGraph: " + sw2.ElapsedMilliseconds + " ms"); sw2.Restart(); @@ -272,7 +275,6 @@ namespace Barotrauma List mainPath = CaveGenerator.GeneratePath(pathNodes, cells, cellGrid, GridCellSize, new Rectangle(pathBorders.X, pathBorders.Y, pathBorders.Width, borders.Height), 0.3f, mirror); - for (int i = 2; i < mainPath.Count; i += 3) { positionsOfInterest.Add(new InterestingPosition(mainPath[i].Center, PositionType.MainPath)); @@ -315,9 +317,9 @@ namespace Barotrauma cells = CleanCells(pathCells); - pathCells.AddRange(CreateBottomHoles(Rand.Range(0.1f,0.8f, false), new Rectangle( + pathCells.AddRange(CreateBottomHoles(generationParams.BottomHoleProbability, new Rectangle( (int)(borders.Width * 0.2f), 0, - (int)(borders.Width * 0.6f), (int)(borders.Height * 0.3f)))); + (int)(borders.Width * 0.6f), (int)(borders.Height * 0.8f)))); foreach (VoronoiCell cell in pathCells) { @@ -408,92 +410,33 @@ namespace Barotrauma cellGrid[x, y].Add(cell); } - - Vector2 ruinSize = new Vector2(Rand.Range(5000.0f, 8000.0f, false), Rand.Range(5000.0f, 8000.0f, false)); - float ruinRadius = Math.Max(ruinSize.X, ruinSize.Y) * 0.5f; - - Vector2 ruinPos = cells[Rand.Int(cells.Count, false)].Center; - - int iter = 0; - - while (mainPath.Any(p => Vector2.Distance(ruinPos, p.Center) < ruinRadius*2.0f)) + for (int i = 0; i 10000.0f) continue; - - Vector2 moveAmount = Vector2.Normalize(ruinPos - pathCell.Center) * 100000.0f / dist; - - //if (weighedPathPos.Y + moveAmount.Y > borders.Bottom - ruinSize.X) - //{ - // moveAmount.X = (Math.Abs(moveAmount.Y) + Math.Abs(moveAmount.X))*Math.Sign(moveAmount.X); - // moveAmount.Y = 0.0f; - //} - - weighedPathPos += moveAmount; - } - - ruinPos = weighedPathPos; - - if (iter > 10000) break; - } - - VoronoiCell closestPathCell = null; - float closestDist = 0.0f; - foreach (VoronoiCell pathCell in mainPath) - { - float dist = Vector2.Distance(pathCell.Center, ruinPos); - if (closestPathCell == null || dist < closestDist) - { - closestPathCell = pathCell; - closestDist = dist; - } - } - - var ruin = new Ruin(closestPathCell, cells, new Rectangle((ruinPos - ruinSize * 0.5f).ToPoint(), ruinSize.ToPoint())); - - ruins = new List(); - ruins.Add(ruin); - - ruin.RuinShapes.Sort((shape1, shape2) => shape2.DistanceFromEntrance.CompareTo(shape1.DistanceFromEntrance)); - for (int i = 0; i < 4; i++ ) - { - positionsOfInterest.Add(new InterestingPosition(ruin.RuinShapes[i].Rect.Center.ToVector2(), PositionType.Ruin)); + GenerateRuin(mainPath); } startPosition.Y = borders.Height; endPosition.Y = borders.Height; List cellsWithBody = new List(cells); - foreach (RuinShape ruinShape in ruin.RuinShapes) - { - var tooClose = GetTooCloseCells(ruinShape.Rect.Center.ToVector2(), Math.Max(ruinShape.Rect.Width, ruinShape.Rect.Height)); - - tooClose.ForEach(c => - { - if (c.edges.Any(e => ruinShape.Rect.Contains(e.point1) || ruinShape.Rect.Contains(e.point2))) c.CellType = CellType.Empty; - }); - } - + List bodyVertices; bodies = CaveGenerator.GeneratePolygons(cellsWithBody, out bodyVertices); renderer.SetBodyVertices(bodyVertices.ToArray()); renderer.SetWallVertices(CaveGenerator.GenerateWallShapes(cells)); - renderer.PlaceSprites(1000); + renderer.PlaceSprites(generationParams.BackgroundSpriteAmount); wrappingWalls = new WrappingWall[2, 2]; + Rectangle ignoredArea = new Rectangle((int)startPosition.X, 0, (int)(endPosition.X - startPosition.X), borders.Height); + for (int side = 0; side < 2; side++) { for (int i = 0; i < 2; i++) { - wrappingWalls[side, i] = new WrappingWall(pathCells, cells, borders.Height * 0.5f, + wrappingWalls[side, i] = new WrappingWall(pathCells, cells, ignoredArea, (side == 0 ? -1 : 1) * (i + 1)); List wrappingWallVertices; @@ -532,27 +475,18 @@ namespace Barotrauma edge.site1 = null; edge.site2 = null; } - } Debug.WriteLine("Generatelevel: " + sw2.ElapsedMilliseconds + " ms"); sw2.Restart(); - - //vertexBuffer = new VertexBuffer(GameMain.CurrGraphicsDevice, VertexPositionTexture.VertexDeclaration, vertices.Length, BufferUsage.WriteOnly); - //vertexBuffer.SetData(vertices); - + if (mirror) { Vector2 temp = startPosition; startPosition = endPosition; endPosition = temp; } - - - //RuinGeneration.RuinGenerator.Draw(spriteBatch); - - Debug.WriteLine("**********************************************************************************"); Debug.WriteLine("Generated a map with " + sites.Count + " sites in " + sw.ElapsedMilliseconds + " ms"); Debug.WriteLine("Seed: "+seed); @@ -569,15 +503,26 @@ namespace Barotrauma if (!limits.Contains(cell.Center)) continue; + float closestDist = 0.0f; + WayPoint closestWayPoint = null; + foreach (WayPoint wp in WayPoint.WayPointList) + { + if (wp.SpawnType != SpawnType.Path) continue; + + float dist =Math.Abs(cell.Center.X - wp.WorldPosition.X); + if (closestWayPoint == null || dist < closestDist) + { + closestDist = dist; + closestWayPoint = wp; + } + } + + if (closestWayPoint.WorldPosition.Y < cell.Center.Y) continue; + toBeRemoved.Add(cell); } return toBeRemoved; - - //foreach (VoronoiCell cell in toBeRemoved) - //{ - // cells.Remove(cell); - //} } private void EnlargeMainPath(List pathCells, float minWidth) @@ -692,22 +637,7 @@ namespace Barotrauma if (tooClose && !tooCloseCells.Contains(cell)) tooCloseCells.Add(cell); } - - //for (float x = -minDistance; x <= minDistance; x+=siteInterval) - //{ - // for (float y = -minDistance; y <= minDistance; y += siteInterval) - // { - // Vector2 cornerPos = position + new Vector2(x,y); - - // int cellIndex = CaveGenerator.FindCellIndex(cornerPos, cells, cellGrid, GridCellSize); - // if (cellIndex == -1) continue; - // if (!tooCloseCells.Contains(cells[cellIndex])) - // { - // tooCloseCells.Add(cells[cellIndex]); - // } - // } - //} - + return tooCloseCells; } @@ -731,6 +661,76 @@ namespace Barotrauma return newCells; } + private void GenerateRuin(List mainPath) + { + + Vector2 ruinSize = new Vector2(Rand.Range(5000.0f, 8000.0f, false), Rand.Range(5000.0f, 8000.0f, false)); + float ruinRadius = Math.Max(ruinSize.X, ruinSize.Y) * 0.5f; + + Vector2 ruinPos = cells[Rand.Int(cells.Count, false)].Center; + + int iter = 0; + + while (mainPath.Any(p => Vector2.Distance(ruinPos, p.Center) < ruinRadius * 2.0f)) + { + Vector2 weighedPathPos = ruinPos; + iter++; + + foreach (VoronoiCell pathCell in mainPath) + { + float dist = Vector2.Distance(pathCell.Center, ruinPos); + if (dist > 10000.0f) continue; + + Vector2 moveAmount = Vector2.Normalize(ruinPos - pathCell.Center) * 100000.0f / dist; + + //if (weighedPathPos.Y + moveAmount.Y > borders.Bottom - ruinSize.X) + //{ + // moveAmount.X = (Math.Abs(moveAmount.Y) + Math.Abs(moveAmount.X))*Math.Sign(moveAmount.X); + // moveAmount.Y = 0.0f; + //} + + weighedPathPos += moveAmount; + } + + ruinPos = weighedPathPos; + + if (iter > 10000) break; + } + + VoronoiCell closestPathCell = null; + float closestDist = 0.0f; + foreach (VoronoiCell pathCell in mainPath) + { + float dist = Vector2.Distance(pathCell.Center, ruinPos); + if (closestPathCell == null || dist < closestDist) + { + closestPathCell = pathCell; + closestDist = dist; + } + } + + var ruin = new Ruin(closestPathCell, cells, new Rectangle((ruinPos - ruinSize * 0.5f).ToPoint(), ruinSize.ToPoint())); + + ruins = new List(); + ruins.Add(ruin); + + ruin.RuinShapes.Sort((shape1, shape2) => shape2.DistanceFromEntrance.CompareTo(shape1.DistanceFromEntrance)); + for (int i = 0; i < 4; i++) + { + positionsOfInterest.Add(new InterestingPosition(ruin.RuinShapes[i].Rect.Center.ToVector2(), PositionType.Ruin)); + } + + foreach (RuinShape ruinShape in ruin.RuinShapes) + { + var tooClose = GetTooCloseCells(ruinShape.Rect.Center.ToVector2(), Math.Max(ruinShape.Rect.Width, ruinShape.Rect.Height)); + + tooClose.ForEach(c => + { + if (c.edges.Any(e => ruinShape.Rect.Contains(e.point1) || ruinShape.Rect.Contains(e.point2))) c.CellType = CellType.Empty; + }); + } + } + public Vector2 GetRandomItemPos(PositionType spawnPosType, float randomSpread, float offsetFromWall = 10.0f) { if (!positionsOfInterest.Any()) return Size*0.5f; @@ -889,7 +889,7 @@ namespace Barotrauma { for (int i = 0; i < 2; i++) { - wrappingWalls[side, i].Dispose(); + if (wrappingWalls[side, i] != null) wrappingWalls[side, i].Dispose(); } } diff --git a/Subsurface/Source/Map/Levels/LevelGenerationParams.cs b/Subsurface/Source/Map/Levels/LevelGenerationParams.cs new file mode 100644 index 000000000..644c894c6 --- /dev/null +++ b/Subsurface/Source/Map/Levels/LevelGenerationParams.cs @@ -0,0 +1,216 @@ +using Microsoft.Xna.Framework; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Xml.Linq; + +namespace Barotrauma +{ + class LevelGenerationParams : IPropertyObject + { + private static List presets; + + public string Name + { + get; + private set; + } + + private float width, height; + + private Vector2 voronoiSiteInterval; + //how much the sites are "scattered" on x- and y-axis + //if Vector2.Zero, the sites will just be placed in a regular grid pattern + private Vector2 voronoiSiteVariance; + + //how far apart the nodes of the main path can be + //x = min interval, y = max interval + private Vector2 mainPathNodeIntervalRange; + + private int smallTunnelCount; + //x = min length, y = max length + private Vector2 smallTunnelLengthRange; + + //how large portion of the bottom of the level should be "carved out" + //if 0.0f, the bottom will be completely solid (making the abyss unreachable) + //if 1.0f, the bottom will be completely open + private float bottomHoleProbability; + + private int ruinCount; + + public Color BackgroundColor + { + get; + set; + } + + [HasDefaultValue(1000, false)] + public int BackgroundSpriteAmount + { + get; + set; + } + + public Dictionary ObjectProperties + { + get; + set; + } + + [HasDefaultValue(100000.0f, false)] + public float Width + { + get { return width; } + set { width = Math.Max(value, 2000.0f); } + } + + [HasDefaultValue(50000.0f, false)] + public float Height + { + get { return height; } + set { height = Math.Max(value, 2000.0f); } + } + + public Vector2 VoronoiSiteInterval + { + get { return voronoiSiteInterval; } + set { + voronoiSiteInterval.X = MathHelper.Clamp(value.X, 100.0f, width/2); + voronoiSiteInterval.Y = MathHelper.Clamp(value.Y, 100.0f, height/2); + } + } + + public Vector2 VoronoiSiteVariance + { + get { return voronoiSiteVariance; } + set + { + voronoiSiteVariance = new Vector2( + MathHelper.Clamp(value.X, 0, voronoiSiteInterval.X), + MathHelper.Clamp(value.Y, 0, voronoiSiteInterval.Y)); + } + } + + public Vector2 MainPathNodeIntervalRange + { + get { return mainPathNodeIntervalRange; } + set + { + mainPathNodeIntervalRange.X = MathHelper.Clamp(value.X, 100.0f, width / 2); + mainPathNodeIntervalRange.Y = MathHelper.Clamp(value.Y, mainPathNodeIntervalRange.X, width / 2); + } + } + + [HasDefaultValue(5, false)] + public int SmallTunnelCount + { + get { return smallTunnelCount; } + set { smallTunnelCount = MathHelper.Clamp(value, 0, 100); } + } + + public Vector2 SmallTunnelLengthRange + { + get { return smallTunnelLengthRange; } + set + { + smallTunnelLengthRange.X = MathHelper.Clamp(value.X, 100.0f, width); + smallTunnelLengthRange.Y = MathHelper.Clamp(value.Y, smallTunnelLengthRange.X, width); + } + } + + [HasDefaultValue(1, false)] + public int RuinCount + { + get { return ruinCount; } + set { ruinCount = MathHelper.Clamp(value, 0, 10); } + } + + [HasDefaultValue(0.4f, false)] + public float BottomHoleProbability + { + get { return bottomHoleProbability; } + set { bottomHoleProbability = MathHelper.Clamp(value, 0.0f, 1.0f); } + } + + //public LevelGenerationParams() + //{ + // Rand.SetSyncedSeed(ToolBox.StringToInt(seed)); + + // width = 100000.0f; + // height = 50000.0f; + + // voronoiSiteInterval = 2000.0f; + // voronoiSiteVariance = new Vector2(voronoiSiteInterval, voronoiSiteInterval) * 0.4f; + + // mainPathNodeIntervalRange = new Vector2(5000.0f, 10000.0f); + + // float brightness = Rand.Range(1.0f, 1.3f, false); + // BackgroundColor = Color.Lerp(new Color(11, 18, 26), new Color(50, 46, 20), Rand.Range(0.0f, 1.0f, false)) * brightness; + // BackgroundColor = new Color(BackgroundColor, 1.0f); + + // smallTunnelCount = 5; + // smallTunnelLengthRange = new Vector2(5000.0f, 10000.0f); + + // ruinCount = 1; + + // bottomHoleProbability = Rand.Range(0.1f, 0.8f, false); + + // BackgroundSpriteAmount = (int)((new Vector2(width, height)).Length() / 100); + //} + + public static LevelGenerationParams GetRandom(string seed) + { + Rand.SetSyncedSeed(ToolBox.StringToInt(seed)); + + if (presets == null || !presets.Any()) + { + DebugConsole.ThrowError("Level generation presets not found - using default presets"); + return new LevelGenerationParams(null); + } + + return presets[Rand.Range(0, presets.Count, false)]; + } + + private LevelGenerationParams(XElement element) + { + Name = element==null ? "default" : element.Name.ToString(); + ObjectProperties = ObjectProperty.InitProperties(this, element); + + Vector3 colorVector = ToolBox.GetAttributeVector3(element, "BackgroundColor", new Vector3(50, 46, 20)); + BackgroundColor = new Color((int)colorVector.X, (int)colorVector.Y, (int)colorVector.Z); + + VoronoiSiteInterval = ToolBox.GetAttributeVector2(element, "VoronoiSiteInterval", new Vector2(3000, 3000)); + + VoronoiSiteVariance = ToolBox.GetAttributeVector2(element, "VoronoiSiteVariance", new Vector2(voronoiSiteInterval.X, voronoiSiteInterval.Y) * 0.4f); + + MainPathNodeIntervalRange = ToolBox.GetAttributeVector2(element, "MainPathNodeIntervalRange", new Vector2(5000.0f, 10000.0f)); + + SmallTunnelLengthRange = ToolBox.GetAttributeVector2(element, "SmallTunnelLengthRange", new Vector2(5000.0f, 10000.0f)); + } + + public static void LoadPresets() + { + presets = new List(); + + var files = GameMain.SelectedPackage.GetFilesOfType(ContentType.LevelGenerationPresets); + if (!files.Any()) + { + files.Add("Content/Map/LevelGenerationParameters.xml"); + } + + foreach (string file in files) + { + + XDocument doc = ToolBox.TryLoadXml(file); + if (doc == null || doc.Root == null) return; + + foreach (XElement element in doc.Root.Elements()) + { + presets.Add(new LevelGenerationParams(element)); + } + } + } + } +} diff --git a/Subsurface/Source/Map/Levels/WrappingWall.cs b/Subsurface/Source/Map/Levels/WrappingWall.cs index cd290a1e5..1c200fa6a 100644 --- a/Subsurface/Source/Map/Levels/WrappingWall.cs +++ b/Subsurface/Source/Map/Levels/WrappingWall.cs @@ -48,28 +48,16 @@ namespace Barotrauma get { return midPos; } } - public WrappingWall(List pathCells, List mapCells, float maxY, int dir = -1) + public WrappingWall(List pathCells, List mapCells, Rectangle ignoredArea, int dir = -1) { cells = new List(); - VoronoiCell lowestPathCell = null; - foreach (VoronoiCell pathCell in pathCells) - { - if (lowestPathCell == null || pathCell.Center.Y < lowestPathCell.Center.Y) - { - lowestPathCell = pathCell; - } - } - - float bottomY = Math.Max(lowestPathCell.Center.Y, maxY); - VoronoiCell edgeCell = null; foreach (VoronoiCell cell in mapCells) { - if (cell.Center.Y > bottomY) continue; - if (edgeCell == null - || (dir < 0 && cell.Center.X < edgeCell.Center.X) - || (dir > 0 && cell.Center.X > edgeCell.Center.X)) + if (ignoredArea.Contains(cell.Center)) continue; + if (Math.Sign(cell.Center.X - ignoredArea.Center.X) != Math.Sign(dir)) continue; + if (edgeCell == null || cell.Center.Y < edgeCell.Center.Y) { edgeCell = cell; } diff --git a/Subsurface/Source/Utils/ToolBox.cs b/Subsurface/Source/Utils/ToolBox.cs index 3370e8e84..ab9df09f1 100644 --- a/Subsurface/Source/Utils/ToolBox.cs +++ b/Subsurface/Source/Utils/ToolBox.cs @@ -61,8 +61,8 @@ namespace Barotrauma } public static object GetAttributeObject(XElement element, string name) - { - if (element.Attribute(name) == null) return null; + { + if (element == null || element.Attribute(name) == null) return null; return GetAttributeObject(element.Attribute(name)); } @@ -106,7 +106,7 @@ namespace Barotrauma public static string GetAttributeString(XElement element, string name, string defaultValue) { - if (element.Attribute(name) == null) return defaultValue; + if (element == null || element.Attribute(name) == null) return defaultValue; return GetAttributeString(element.Attribute(name), defaultValue); } @@ -119,7 +119,7 @@ namespace Barotrauma public static float GetAttributeFloat(XElement element, string name, float defaultValue) { - if (element.Attribute(name) == null) return defaultValue; + if (element == null || element.Attribute(name) == null) return defaultValue; float val = defaultValue; @@ -158,7 +158,7 @@ namespace Barotrauma public static int GetAttributeInt(XElement element, string name, int defaultValue) { - if (element.Attribute(name) == null) return defaultValue; + if (element == null || element.Attribute(name) == null) return defaultValue; int val = defaultValue; @@ -176,10 +176,9 @@ namespace Barotrauma public static bool GetAttributeBool(XElement element, string name, bool defaultValue) { - var attribute = element.Attribute(name); - if (attribute == null) return defaultValue; + if (element == null || element.Attribute(name) == null) return defaultValue; - return GetAttributeBool(attribute, defaultValue); + return GetAttributeBool(element.Attribute(name), defaultValue); } public static bool GetAttributeBool(XAttribute attribute, bool defaultValue) @@ -206,16 +205,25 @@ namespace Barotrauma public static Vector2 GetAttributeVector2(XElement element, string name, Vector2 defaultValue) { - if (element.Attribute(name) == null) return defaultValue; + if (element == null || element.Attribute(name) == null) return defaultValue; string val = element.Attribute(name).Value; return ParseToVector2(val); } + + public static Vector3 GetAttributeVector3(XElement element, string name, Vector3 defaultValue) + { + if (element == null || element.Attribute(name) == null) return defaultValue; + + string val = element.Attribute(name).Value; + + return ParseToVector3(val); + } public static Vector4 GetAttributeVector4(XElement element, string name, Vector4 defaultValue) { - if (element.Attribute(name) == null) return defaultValue; + if (element == null || element.Attribute(name) == null) return defaultValue; string val = element.Attribute(name).Value; @@ -259,6 +267,26 @@ namespace Barotrauma return vector.X.ToString("G", CultureInfo.InvariantCulture) + "," + vector.Y.ToString("G", CultureInfo.InvariantCulture); } + public static Vector3 ParseToVector3(string stringVector3, bool errorMessages = true) + { + string[] components = stringVector3.Split(','); + + Vector3 vector = Vector3.Zero; + + if (components.Length!=3) + { + if (!errorMessages) return vector; + DebugConsole.ThrowError("Failed to parse the string ''"+stringVector3+"'' to Vector3"); + return vector; + } + + float.TryParse(components[0], NumberStyles.Any, CultureInfo.InvariantCulture, out vector.X); + float.TryParse(components[1], NumberStyles.Any, CultureInfo.InvariantCulture, out vector.Y); + float.TryParse(components[2], NumberStyles.Any, CultureInfo.InvariantCulture, out vector.Z); + + return vector; + } + public static Vector4 ParseToVector4(string stringVector4, bool errorMessages = true) { string[] components = stringVector4.Split(',');