From 37a58881262bdbe1dcce0949d748816cb6a4938d Mon Sep 17 00:00:00 2001 From: Joonas Rikkonen Date: Sun, 27 Aug 2017 13:40:47 +0300 Subject: [PATCH] - More small caves in levels. - Groups of crawlers, mantises and husks can spawn inside the caves. - Salvage mission variants where the artifact spawns inside a cave. - Fixed ruins being placed inside the sea floor. - MonsterEvents don't spawn the monsters if no suitable spawn position is found. --- .../Source/Map/Levels/LevelRenderer.cs | 13 + .../Content/Map/LevelGenerationParameters.xml | 12 +- .../BarotraumaShared/Content/Missions.xml | 26 ++ .../BarotraumaShared/Content/randomevents.xml | 32 +- .../Source/Events/Missions/MonsterMission.cs | 3 +- .../Source/Events/MonsterEvent.cs | 11 +- .../Source/Map/Levels/Level.cs | 297 +++++++++++------- 7 files changed, 263 insertions(+), 131 deletions(-) diff --git a/Barotrauma/BarotraumaClient/Source/Map/Levels/LevelRenderer.cs b/Barotrauma/BarotraumaClient/Source/Map/Levels/LevelRenderer.cs index b0a315e67..1d816503f 100644 --- a/Barotrauma/BarotraumaClient/Source/Map/Levels/LevelRenderer.cs +++ b/Barotrauma/BarotraumaClient/Source/Map/Levels/LevelRenderer.cs @@ -1,6 +1,7 @@ using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; using System; +using System.Collections.Generic; using Voronoi2; namespace Barotrauma @@ -190,6 +191,18 @@ namespace Barotrauma GUI.DrawRectangle(spriteBatch, new Vector2(point.X, -point.Y), new Vector2(10.0f, 10.0f), Color.White, true); } } + + foreach (List nodeList in level.SmallTunnels) + { + for (int i = 1; i + + + + + + + + + + + + + + + + + > smallTunnels = new List>(); + private static BackgroundSpriteManager backgroundSpriteManager; public Vector2 StartPosition @@ -113,6 +115,11 @@ namespace Barotrauma get { return extraWalls; } } + public List> SmallTunnels + { + get { return smallTunnels; } + } + public string Seed { get { return seed; } @@ -137,6 +144,7 @@ namespace Barotrauma } + public LevelGenerationParams GenerationParams { get { return generationParams; } @@ -227,6 +235,8 @@ namespace Barotrauma GameMain.LightManager.AmbientLight = new Color(backgroundColor * (10.0f / avgValue), 1.0f); #endif + SeaFloorTopPos = generationParams.SeaFloorDepth + generationParams.MountainHeightMax + generationParams.SeaFloorVariance; + float minWidth = 6500.0f; if (Submarine.MainSub != null) { @@ -234,6 +244,9 @@ namespace Barotrauma minWidth = Math.Max(minWidth, Math.Max(dockedSubBorders.Width, dockedSubBorders.Height)); } + Rectangle pathBorders = borders; + pathBorders.Inflate(-minWidth * 2, -minWidth * 2); + startPosition = new Vector2( Rand.Range(minWidth, minWidth * 2, Rand.RandSync.Server), Rand.Range(borders.Height * 0.5f, borders.Height - minWidth * 2, Rand.RandSync.Server)); @@ -241,11 +254,12 @@ namespace Barotrauma endPosition = new Vector2( borders.Width - Rand.Range(minWidth, minWidth * 2, Rand.RandSync.Server), Rand.Range(borders.Height * 0.5f, borders.Height - minWidth * 2, Rand.RandSync.Server)); - - 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); + //---------------------------------------------------------------------------------- + //generate the initial nodes for the main path and smaller tunnels + //---------------------------------------------------------------------------------- + + List pathNodes = new List(); pathNodes.Add(new Vector2(startPosition.X, borders.Height)); Vector2 nodeInterval = generationParams.MainPathNodeIntervalRange; @@ -264,32 +278,12 @@ namespace Barotrauma pathNodes.Add((startPosition + endPosition) / 2); } - List> smallTunnels = new List>(); - for (int i = 0; i < generationParams.SmallTunnelCount; i++) - { - var tunnelStartPos = pathNodes[Rand.Range(2, pathNodes.Count - 2, Rand.RandSync.Server)]; - tunnelStartPos.X = MathHelper.Clamp(tunnelStartPos.X, pathBorders.X, pathBorders.Right); + GenerateTunnels(pathNodes, minWidth); - float tunnelLength = Rand.Range( - generationParams.SmallTunnelLengthRange.X, - generationParams.SmallTunnelLengthRange.Y, - Rand.RandSync.Server); + //---------------------------------------------------------------------------------- + //generate voronoi sites + //---------------------------------------------------------------------------------- - var tunnelNodes = MathUtils.GenerateJaggedLine( - tunnelStartPos, - new Vector2(tunnelStartPos.X, pathBorders.Bottom)+Rand.Vector(tunnelLength, Rand.RandSync.Server), - 4, 1000.0f); - - List tunnel = new List(); - foreach (Vector2[] tunnelNode in tunnelNodes) - { - if (!pathBorders.Contains(tunnelNode[0])) continue; - tunnel.Add(tunnelNode[0]); - } - - if (tunnel.Any()) smallTunnels.Add(tunnel); - } - Vector2 siteInterval = generationParams.VoronoiSiteInterval; Vector2 siteVariance = generationParams.VoronoiSiteVariance; for (float x = siteInterval.X / 2; x < borders.Width; x += siteInterval.X) @@ -314,6 +308,11 @@ namespace Barotrauma } } + + //---------------------------------------------------------------------------------- + // construct the voronoi graph and cells + //---------------------------------------------------------------------------------- + Stopwatch sw2 = new Stopwatch(); sw2.Start(); @@ -328,6 +327,10 @@ namespace Barotrauma Debug.WriteLine("find cells: " + sw2.ElapsedMilliseconds + " ms"); sw2.Restart(); + //---------------------------------------------------------------------------------- + // generate a path through the initial path nodes + //---------------------------------------------------------------------------------- + List mainPath = CaveGenerator.GeneratePath(pathNodes, cells, cellGrid, GridCellSize, new Rectangle(pathBorders.X, pathBorders.Y, pathBorders.Width, borders.Height), 0.5f, mirror); @@ -338,6 +341,7 @@ namespace Barotrauma List pathCells = new List(mainPath); + //make sure the path is wide enough to pass through EnlargeMainPath(pathCells, minWidth); foreach (InterestingPosition positionOfInterest in positionsOfInterest) @@ -348,9 +352,13 @@ namespace Barotrauma startPosition.X = pathCells[0].Center.X; + //---------------------------------------------------------------------------------- + // tunnels through the tunnel nodes + //---------------------------------------------------------------------------------- + foreach (List tunnel in smallTunnels) { - if (tunnel.Count<2) continue; + if (tunnel.Count < 2) continue; //find the cell which the path starts from int startCellIndex = CaveGenerator.FindCellIndex(tunnel[0], cells, cellGrid, GridCellSize, 1); @@ -370,9 +378,13 @@ namespace Barotrauma Debug.WriteLine("path: " + sw2.ElapsedMilliseconds + " ms"); sw2.Restart(); - - cells = CleanCells(pathCells); - + + + //---------------------------------------------------------------------------------- + // remove unnecessary cells and create some holes at the bottom of the level + //---------------------------------------------------------------------------------- + + cells = CleanCells(pathCells); pathCells.AddRange(CreateBottomHoles(generationParams.BottomHoleProbability, new Rectangle( (int)(borders.Width * 0.2f), 0, (int)(borders.Width * 0.6f), (int)(borders.Height * 0.8f)))); @@ -383,6 +395,10 @@ namespace Barotrauma cell.edges.ForEach(e => e.OutsideLevel = true); } + //---------------------------------------------------------------------------------- + // initialize the cells that are still left and insert them into the cell grid + //---------------------------------------------------------------------------------- + foreach (VoronoiCell cell in pathCells) { cell.edges.ForEach(e => e.OutsideLevel = false); @@ -391,71 +407,6 @@ namespace Barotrauma cells.Remove(cell); } - //generate some narrow caves - int caveAmount = 0;// Rand.Int(3, false); - List usedCaveCells = new List(); - for (int i = 0; i < caveAmount; i++) - { - Vector2 startPoint = Vector2.Zero; - VoronoiCell startCell = null; - - var caveCells = new List(); - - int maxTries = 5, tries = 0; - while (tries pathCells.Contains(e.AdjacentCell(startCell))); - - if (startEdge != null) - { - startPoint = (startEdge.point1 + startEdge.point2) / 2.0f; - startPoint += startPoint - startCell.Center; - - //get the cells in which the cave will be carved - caveCells = GetCells(startCell.Center, 2); - //remove cells that have already been "carved" out - caveCells.RemoveAll(c => c.CellType == CellType.Path); - - //if any of the cells have already been used as a cave, continue and find some other cells - if (usedCaveCells.Any(c => caveCells.Contains(c))) continue; - break; - } - - tries++; - } - - //couldn't find a place for a cave -> abort - if (tries >= maxTries) break; - - if (!caveCells.Any()) continue; - - usedCaveCells.AddRange(caveCells); - - List caveSolidCells; - var cavePathCells = CaveGenerator.CarveCave(caveCells, startPoint, out caveSolidCells); - - //remove the large cells used as a "base" for the cave (they've now been replaced with smaller ones) - caveCells.ForEach(c => cells.Remove(c)); - - cells.AddRange(caveSolidCells); - - foreach (VoronoiCell cell in cavePathCells) - { - cells.Remove(cell); - } - - pathCells.AddRange(cavePathCells); - - for (int j = cavePathCells.Count / 2; j < cavePathCells.Count; j += 10) - { - positionsOfInterest.Add(new InterestingPosition(cavePathCells[j].Center, PositionType.Cave)); - } - } - for (int x = 0; x < cellGrid.GetLength(0); x++) { for (int y = 0; y < cellGrid.GetLength(1); y++) @@ -474,12 +425,22 @@ namespace Barotrauma cellGrid[x, y].Add(cell); } + + //---------------------------------------------------------------------------------- + // create some ruins + //---------------------------------------------------------------------------------- + ruins = new List(); for (int i = 0; i < generationParams.RuinCount; i++) { GenerateRuin(mainPath); } + + //---------------------------------------------------------------------------------- + // generate the bodies and rendered triangles of the cells + //---------------------------------------------------------------------------------- + startPosition.Y = borders.Height; endPosition.Y = borders.Height; @@ -523,20 +484,20 @@ namespace Barotrauma //initialize MapEntities that aren't in any sub (e.g. items inside ruins) MapEntity.MapLoaded(null); - + Debug.WriteLine("Generatelevel: " + sw2.ElapsedMilliseconds + " ms"); sw2.Restart(); - + if (mirror) { Vector2 temp = startPosition; startPosition = endPosition; endPosition = temp; } - + Debug.WriteLine("**********************************************************************************"); Debug.WriteLine("Generated a map with " + sites.Count + " sites in " + sw.ElapsedMilliseconds + " ms"); - Debug.WriteLine("Seed: "+seed); + Debug.WriteLine("Seed: " + seed); Debug.WriteLine("**********************************************************************************"); } @@ -714,7 +675,6 @@ namespace Barotrauma } bottomPositions.Add(new Vector2(Size.X, BottomPos)); - float minVertexInterval = 5000.0f; float currInverval = Size.X / 2.0f; while (currInverval > minVertexInterval) @@ -746,6 +706,97 @@ namespace Barotrauma bodies.Add(BottomBarrier); } + private void GenerateTunnels(List pathNodes, float pathWidth) + { + smallTunnels = new List>(); + for (int i = 0; i < generationParams.SmallTunnelCount; i++) + { + int startNodeIndex = Rand.Range(1, pathNodes.Count - 2, Rand.RandSync.Server); + var tunnelStartPos = Vector2.Lerp(pathNodes[startNodeIndex], pathNodes[startNodeIndex + 1], Rand.Range(0.0f, 1.0f, Rand.RandSync.Server)); + + float tunnelLength = Rand.Range( + generationParams.SmallTunnelLengthRange.X, + generationParams.SmallTunnelLengthRange.Y, + Rand.RandSync.Server); + + List tunnelNodes = new List() + { + tunnelStartPos, + tunnelStartPos + Vector2.UnitY * Math.Sign(tunnelStartPos.Y - Size.Y / 2) * pathWidth * 2 + }; + + List tunnel = GenerateTunnel( + tunnelNodes, + Rand.Range(generationParams.SmallTunnelLengthRange.X, generationParams.SmallTunnelLengthRange.Y, Rand.RandSync.Server), + pathNodes); + if (tunnel.Any()) smallTunnels.Add(tunnel); + + int branches = Rand.Range(0, 3, Rand.RandSync.Server); + for (int j = 0; j < branches; j++) + { + List branch = GenerateTunnel( + new List() { tunnel[Rand.Int(tunnel.Count, Rand.RandSync.Server)] }, + Rand.Range(generationParams.SmallTunnelLengthRange.X, generationParams.SmallTunnelLengthRange.Y, Rand.RandSync.Server) * 0.5f, + pathNodes); + if (branch.Any()) smallTunnels.Add(branch); + } + + } + } + + private List GenerateTunnel(List tunnelNodes, float tunnelLength, List avoidNodes) + { + float sectionLength = 1000.0f; + + float currLength = 0.0f; + while (currLength < tunnelLength) + { + Vector2 dir = Rand.Vector(1.0f, Rand.RandSync.Server); + + dir.Y += Math.Sign(tunnelNodes[tunnelNodes.Count - 1].Y - Size.Y / 2) * 0.5f; + if (tunnelNodes.Count > 1) + { + dir += Vector2.Normalize(tunnelNodes[tunnelNodes.Count - 1] - tunnelNodes[tunnelNodes.Count - 2]) * 0.5f; + } + + float avoidDist = 20000.0f; + foreach (Vector2 pathNode in avoidNodes) + { + Vector2 diff = tunnelNodes[tunnelNodes.Count - 1] - pathNode; + if (diff == Vector2.Zero) continue; + + float dist = diff.Length(); + if (dist < avoidDist) + { + dir += (diff / dist) * (1.0f - dist / avoidDist); + } + } + + Vector2 normalizedDir = Vector2.Normalize(dir); + + if (tunnelNodes.Last().Y + normalizedDir.Y > Size.Y) + { + //head back down if the tunnel has reached the top of the level + normalizedDir.Y = -normalizedDir.Y; + } + else if (tunnelNodes.Last().Y + normalizedDir.Y + normalizedDir.Y < 0.0f || + tunnelNodes.Last().Y + normalizedDir.Y + normalizedDir.Y < SeaFloorTopPos) + { + //head back up if reached the sea floor + normalizedDir.Y = -normalizedDir.Y; + } + + Vector2 nextNode = tunnelNodes.Last() + normalizedDir * sectionLength; + + nextNode.X = MathHelper.Clamp(nextNode.X, 500.0f, Size.X - 500.0f); + nextNode.Y = MathHelper.Clamp(nextNode.Y, SeaFloorTopPos, Size.Y - 500.0f); + tunnelNodes.Add(nextNode); + currLength += sectionLength; + } + + return tunnelNodes; + } + private void GenerateRuin(List mainPath) { Vector2 ruinSize = new Vector2(Rand.Range(5000.0f, 8000.0f, Rand.RandSync.Server), Rand.Range(5000.0f, 8000.0f, Rand.RandSync.Server)); @@ -753,10 +804,16 @@ namespace Barotrauma Vector2 ruinPos = cells[Rand.Int(cells.Count, Rand.RandSync.Server)].Center; + //50% chance of placing the ruins at a cave + if (Rand.Range(0.0f, 1.0f, Rand.RandSync.Server) < 0.5f) + { + TryGetInterestingPosition(true, PositionType.Cave, false, out ruinPos); + } + + ruinPos.Y = Math.Min(ruinPos.Y, borders.Y + borders.Height - ruinSize.Y / 2); + ruinPos.Y = Math.Max(ruinPos.Y, SeaFloorTopPos + ruinSize.Y / 2.0f); + int iter = 0; - - ruinPos.Y = Math.Min(borders.Y + borders.Height - ruinSize.Y/2, ruinPos.Y); - while (mainPath.Any(p => Vector2.Distance(ruinPos, p.Center) < ruinRadius * 2.0f)) { Vector2 weighedPathPos = ruinPos; @@ -768,13 +825,7 @@ namespace Barotrauma 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; weighedPathPos.Y = Math.Min(borders.Y + borders.Height - ruinSize.Y / 2, weighedPathPos.Y); } @@ -816,7 +867,8 @@ namespace Barotrauma { Rectangle rect = ruinShape.Rect; rect.Y += rect.Height; - if (MathUtils.GetLineRectangleIntersection(e.point1, e.point2, rect) != null) + if (ruinShape.Rect.Contains(e.point1) || ruinShape.Rect.Contains(e.point2) || + MathUtils.GetLineRectangleIntersection(e.point1, e.point2, rect) != null) { cell.CellType = CellType.Removed; @@ -843,7 +895,8 @@ namespace Barotrauma int tries = 0; do { - Vector2 startPos = Level.Loaded.GetRandomInterestingPosition(true, spawnPosType, true); + Vector2 startPos; + Level.Loaded.TryGetInterestingPosition(true, spawnPosType, true, out startPos); startPos += Rand.Vector(Rand.Range(0.0f, randomSpread, Rand.RandSync.Server), Rand.RandSync.Server); @@ -870,9 +923,15 @@ namespace Barotrauma return position; } - public Vector2 GetRandomInterestingPosition(bool useSyncedRand, PositionType positionType, bool avoidSubs) + + + public bool TryGetInterestingPosition(bool useSyncedRand, PositionType positionType, bool avoidSubs, out Vector2 position) { - if (!positionsOfInterest.Any()) return Size * 0.5f; + if (!positionsOfInterest.Any()) + { + position = Size * 0.5f; + return false; + } var matchingPositions = positionsOfInterest.FindAll(p => positionType.HasFlag(p.PositionType)); @@ -887,10 +946,12 @@ namespace Barotrauma if (!matchingPositions.Any()) { - return positionsOfInterest[Rand.Int(positionsOfInterest.Count, (useSyncedRand ? Rand.RandSync.Server : Rand.RandSync.Unsynced))].Position; + position = positionsOfInterest[Rand.Int(positionsOfInterest.Count, (useSyncedRand ? Rand.RandSync.Server : Rand.RandSync.Unsynced))].Position; + return false; } - return matchingPositions[Rand.Int(matchingPositions.Count, (useSyncedRand ? Rand.RandSync.Server : Rand.RandSync.Unsynced))].Position; + position = matchingPositions[Rand.Int(matchingPositions.Count, (useSyncedRand ? Rand.RandSync.Server : Rand.RandSync.Unsynced))].Position; + return true; } public void Update(float deltaTime, Camera cam)