58a14fd054
commit bd9a92df11a8d83d0d0087ce64422f2fba3f6f99 Author: Joonas Rikkonen <poe.regalis@gmail.com> Date: Sat Feb 2 20:07:36 2019 +0200 v0.8.9.2 commit 4e1d5fa3744fb8cab80cb006a433d2efe7685562 Author: Joonas Rikkonen <poe.regalis@gmail.com> Date: Sat Feb 2 19:20:03 2019 +0200 Moved humpback's cargo spawn position a bit to prevent the cargo from falling down the hatch under it. commit f0ec5530b0c7ecbdaaf8bd266d9a57fac2c92fe9 Author: Joonas Rikkonen <poe.regalis@gmail.com> Date: Sat Feb 2 19:18:41 2019 +0200 Fixed structure BodyOffset not being taken into account when generating the collider for the sub. Caused the button outside Orca's button to be impossible to interact with. commit 1360f5511522280d03d608c8eb6f56fb6608aab5 Author: ezjamsen <ezjames.fi@gmail.com> Date: Sat Feb 2 19:10:37 2019 +0200 Halved the status effect for damage to submerged items. This might be a bit too generous, let's see. commit 06afb668bc816a345dbd12899b819843549e908a Author: Joonas Rikkonen <poe.regalis@gmail.com> Date: Sat Feb 2 18:22:00 2019 +0200 Changed mantis loading tip to mud raptor, fixed a glaring error in one of the npc conversations commit d2dfa960dd5fa43426eb4dfd02de6c497ae9d838 Author: Joonas Rikkonen <poe.regalis@gmail.com> Date: Sat Feb 2 18:17:17 2019 +0200 Fixed water flow forces occasionally being way too high due to bugged force calculation logic when water is flowing from a hull to the one below it. + Added a hard cap to flow forces to prevent this from happening again. Closes #572 commit 21754b39ded247e8c9837d58c6de9658f56a9772 Author: Joonas Rikkonen <poe.regalis@gmail.com> Date: Sat Feb 2 17:24:48 2019 +0200 Entities cannot be placed in the sub editor when the cursor is on a UI element. Fixes an entity being placed when selecting an item from them menu while something else is already selected. Closes #1023 commit 97d0cf92f738a058fa0140ec841549a5d6c746a3 Author: Joonas Rikkonen <poe.regalis@gmail.com> Date: Sat Feb 2 16:59:56 2019 +0200 Fixed a typo in one of the affliction descriptions commit ddccbdc9a438199d3cb796789dd9915cc305ec5b Author: Joonas Rikkonen <poe.regalis@gmail.com> Date: Sat Feb 2 16:59:18 2019 +0200 Fixed sliders buttons becoming invisible when pressed commit bc28473a1c61698c9447825b1c5a13edd6e7bf6c Merge: 2d212802e f3dfe5990 Author: ezjamsen <ezjames.fi@gmail.com> Date: Sat Feb 2 16:45:33 2019 +0200 Merge branch 'dev' of https://github.com/Regalis11/Barotrauma into dev commit 2d212802ea945c6cbd1c889d304595b68692e05c Author: ezjamsen <ezjames.fi@gmail.com> Date: Sat Feb 2 16:44:30 2019 +0200 corrected the condition at which railgun and coilgun loaders can be repaired commit f3dfe5990a03cb3c577635dffb25883085d919f1 Author: Joonas Rikkonen <poe.regalis@gmail.com> Date: Sat Feb 2 16:41:34 2019 +0200 Fixed second submarine overlapping with the outpost at the end of the level in combat missions. commit 1f224e9b7cae3f94e9b6937d66c171edbd8ef523 Author: Joonas Rikkonen <poe.regalis@gmail.com> Date: Sat Feb 2 15:35:22 2019 +0200 Fixed sub/mode selection setting tickboxes commit 0eb99486454c1bb47b51b20e4d23f42f4147dd8d Merge: ff90fdb35 80785d627 Author: Joonas Rikkonen <poe.regalis@gmail.com> Date: Sat Feb 2 15:08:18 2019 +0200 Merge branch 'dev' of https://github.com/Regalis11/Barotrauma into dev commit ff90fdb352af6800d0dcf224816cf9e831184f1e Author: Joonas Rikkonen <poe.regalis@gmail.com> Date: Sat Feb 2 15:08:12 2019 +0200 Don't do chat message similarity tests on order messages (caused spam kicks way too easily when giving orders or reporting things) commit af8a350c7998ae2c729b4354c3c02cbcf75cabde Author: Joonas Rikkonen <poe.regalis@gmail.com> Date: Sat Feb 2 15:06:42 2019 +0200 Fixed Huskappendage.xml not being copied to the output directory commit df3af76d909099e9c9e59f74b1f5b3b381303a99 Author: Joonas Rikkonen <poe.regalis@gmail.com> Date: Sat Feb 2 14:06:19 2019 +0200 Revert "Ignore anything but the content path when checking the filepath cases." Doesn't work correctly if there's a folder named "Content" in some subfolder (e.g. "Mods/SomeMod/Content") or if checking file paths somewhere else than the content folder (Submarines, Data, NewWorkshopItem, Mods...) This reverts commit 123e84ea1ac6f4392c293d0d5c64b857bfc4eb30. commit 80785d627c82948ce1784654070a0da8f732ca33 Author: ezjamsen <ezjames.fi@gmail.com> Date: Sat Feb 2 14:43:36 2019 +0200 added better visual feedback when reactor is in poor condition commit e450872721d8e0b47b3ae5792292ea2071a8eefe Author: ezjamsen <ezjames.fi@gmail.com> Date: Sat Feb 2 14:40:42 2019 +0200 added a small stun effect to railgun shells commit 05792285eab781990bdc3b658a9217ff38704d1e Author: ezjamsen <ezjames.fi@gmail.com> Date: Sat Feb 2 14:40:03 2019 +0200 added a little extra fuel, moved the position of guns to hull to prevent players getting caught under them commit 8c85c33023b6cec1c18b8743d1f05fd4fc8c2ea7 Author: Joonas Rikkonen <poe.regalis@gmail.com> Date: Sat Feb 2 13:58:55 2019 +0200 Fixed crashing when attempting to use active sonar in the sub editor. Closes #1024 commit d71225d8c58fb285cbf207549b77cabdc7e1df11 Author: Joonas Rikkonen <poe.regalis@gmail.com> Date: Fri Feb 1 18:27:06 2019 +0200 Fixed crashing when selecting an item in a fabricator that's linked to containers, but not set to display the contents of the containers commit a6fa5d82c7b1505932b8283ccfef75798b839d92 Author: Joonas Rikkonen <poe.regalis@gmail.com> Date: Fri Feb 1 18:07:40 2019 +0200 Fixed "can't create and entity event for Hull" error messages when attempting to remove a hull with fire sources in it in MP commit 72737c6d3eb7ca7daf5e5d84fdcbc1f060b79b10 Author: Joonas Rikkonen <poe.regalis@gmail.com> Date: Fri Feb 1 18:05:04 2019 +0200 Filename case fix part 913, fixed rewards mentioned in mission descriptions not matching the actual reward of the mission. commit a148c7843f2805b1757d231bcf028bafdbc22187 Author: Joonas Rikkonen <poe.regalis@gmail.com> Date: Fri Feb 1 17:56:18 2019 +0200 Attempt to forward steam query port when UPnP is enabled commit 3e73d705c221a417f60baf76c8c7d100391f7802 Author: Joonas Rikkonen <poe.regalis@gmail.com> Date: Fri Feb 1 17:44:34 2019 +0200 Removed the "training" button from the main menu (not needed, tutorial is now implemented directly in the SP campaign) commit 1f07c3f1b4de0bb09fe11e9834faeea30e4afade Author: Joonas Rikkonen <poe.regalis@gmail.com> Date: Fri Feb 1 17:33:01 2019 +0200 Fixed crewmanager UI elements hidden outside the extents of the crew list overlapping with other UI elements commit aa0c5f4e90b57a8ee3f152b9aeba1e73c7c539c0 Merge: 123e84ea1 73f51a405 Author: itchyOwl <lauri.harkanen@gmail.com> Date: Fri Feb 1 16:21:27 2019 +0200 Merge branch 'dev' of https://github.com/Regalis11/Barotrauma into dev commit 123e84ea1ac6f4392c293d0d5c64b857bfc4eb30 Author: itchyOwl <lauri.harkanen@gmail.com> Date: Fri Feb 1 16:21:19 2019 +0200 Ignore anything but the content path when checking the filepath cases. commit 73f51a4055ca7fd1cd65570a51d88db1f1272d23 Author: Joonas Rikkonen <poe.regalis@gmail.com> Date: Fri Feb 1 15:20:15 2019 +0200 Don't show the "incorrect case" errors in non-linux release builds. It seems that Path.GetFullPath may return paths with an incorrect case on Windows (try changing the case of any of the game's parent folders to repro). commit 5a598129a9d63c5185529107d469404c35be59c5 Author: ezjamsen <ezjames.fi@gmail.com> Date: Fri Feb 1 11:29:41 2019 +0200 Renamed hull areas... again.
1577 lines
62 KiB
C#
1577 lines
62 KiB
C#
using Barotrauma.Extensions;
|
|
using Barotrauma.Items.Components;
|
|
using Barotrauma.Networking;
|
|
using Barotrauma.RuinGeneration;
|
|
using FarseerPhysics;
|
|
using FarseerPhysics.Dynamics;
|
|
using FarseerPhysics.Factories;
|
|
using Lidgren.Network;
|
|
using Microsoft.Xna.Framework;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Diagnostics;
|
|
using System.Linq;
|
|
using Voronoi2;
|
|
|
|
namespace Barotrauma
|
|
{
|
|
partial class Level : Entity, IServerSerializable
|
|
{
|
|
//all entities are disabled after they reach this depth
|
|
public const int MaxEntityDepth = -300000;
|
|
public const float ShaftHeight = 1000.0f;
|
|
|
|
public static Level Loaded
|
|
{
|
|
get { return loaded; }
|
|
}
|
|
|
|
[Flags]
|
|
public enum PositionType
|
|
{
|
|
MainPath = 1, Cave = 2, Ruin = 4
|
|
}
|
|
|
|
public struct InterestingPosition
|
|
{
|
|
public Point Position;
|
|
public readonly PositionType PositionType;
|
|
|
|
public InterestingPosition(Point position, PositionType positionType)
|
|
{
|
|
Position = position;
|
|
PositionType = positionType;
|
|
}
|
|
}
|
|
|
|
static Level loaded;
|
|
|
|
//how close the sub has to be to start/endposition to exit
|
|
public const float ExitDistance = 6000.0f;
|
|
|
|
private string seed;
|
|
|
|
public const int GridCellSize = 2000;
|
|
private List<VoronoiCell>[,] cellGrid;
|
|
|
|
private List<LevelWall> extraWalls;
|
|
|
|
private LevelWall seaFloor;
|
|
|
|
private List<VoronoiCell> cells;
|
|
|
|
private Point startPosition, endPosition;
|
|
|
|
private Rectangle borders;
|
|
|
|
private List<Body> bodies;
|
|
|
|
private List<InterestingPosition> positionsOfInterest;
|
|
|
|
private List<Ruin> ruins;
|
|
|
|
private LevelGenerationParams generationParams;
|
|
|
|
private List<List<Point>> smallTunnels = new List<List<Point>>();
|
|
|
|
private LevelObjectManager levelObjectManager;
|
|
|
|
private List<Point> bottomPositions;
|
|
|
|
//no need for frequent network updates, as currently the only thing that's synced
|
|
//are the slowly moving ice chunks that move in a very predictable way
|
|
const float NetworkUpdateInterval = 5.0f;
|
|
private float networkUpdateTimer;
|
|
|
|
public Vector2 StartPosition
|
|
{
|
|
get { return startPosition.ToVector2(); }
|
|
}
|
|
|
|
public Point Size
|
|
{
|
|
get { return new Point(borders.Width, borders.Height); }
|
|
}
|
|
|
|
public Vector2 EndPosition
|
|
{
|
|
get { return endPosition.ToVector2(); }
|
|
}
|
|
|
|
public int BottomPos
|
|
{
|
|
get;
|
|
private set;
|
|
}
|
|
|
|
public int SeaFloorTopPos
|
|
{
|
|
get;
|
|
private set;
|
|
}
|
|
|
|
public LevelWall SeaFloor
|
|
{
|
|
get { return seaFloor; }
|
|
}
|
|
|
|
public List<Ruin> Ruins
|
|
{
|
|
get { return ruins; }
|
|
}
|
|
|
|
public List<LevelWall> ExtraWalls
|
|
{
|
|
get { return extraWalls; }
|
|
}
|
|
|
|
public List<List<Point>> SmallTunnels
|
|
{
|
|
get { return smallTunnels; }
|
|
}
|
|
|
|
public List<InterestingPosition> PositionsOfInterest
|
|
{
|
|
get { return positionsOfInterest; }
|
|
}
|
|
|
|
public Submarine StartOutpost { get; private set; }
|
|
public Submarine EndOutpost { get; private set; }
|
|
|
|
public string Seed
|
|
{
|
|
get { return seed; }
|
|
}
|
|
|
|
public Biome Biome;
|
|
|
|
/// <summary>
|
|
/// A random integer assigned at the end of level generation. If these values differ between clients/server,
|
|
/// it means the levels aren't identical for some reason and there will most likely be major ID mismatches.
|
|
/// </summary>
|
|
public int EqualityCheckVal
|
|
{
|
|
get;
|
|
private set;
|
|
}
|
|
|
|
public float Difficulty
|
|
{
|
|
get;
|
|
private set;
|
|
}
|
|
|
|
public Body TopBarrier
|
|
{
|
|
get;
|
|
private set;
|
|
}
|
|
|
|
public Body BottomBarrier
|
|
{
|
|
get;
|
|
private set;
|
|
}
|
|
|
|
public LevelObjectManager LevelObjectManager
|
|
{
|
|
get { return levelObjectManager; }
|
|
}
|
|
|
|
public bool Mirrored
|
|
{
|
|
get;
|
|
private set;
|
|
}
|
|
|
|
public LevelGenerationParams GenerationParams
|
|
{
|
|
get { return generationParams; }
|
|
}
|
|
|
|
public Color BackgroundTextureColor
|
|
{
|
|
get { return generationParams.BackgroundTextureColor; }
|
|
}
|
|
|
|
public Color BackgroundColor
|
|
{
|
|
get { return generationParams.BackgroundColor; }
|
|
}
|
|
|
|
public Color WallColor
|
|
{
|
|
get { return generationParams.WallColor; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Instantiates a level (the Generate-function still needs to be called before the level is playable)
|
|
/// </summary>
|
|
/// <param name="difficulty">A scalar between 0-100</param>
|
|
/// <param name="sizeFactor">A scalar between 0-1 (0 = the minimum width defined in the generation params is used, 1 = the max width is used)</param>
|
|
public Level(string seed, float difficulty, float sizeFactor, LevelGenerationParams generationParams, Biome biome)
|
|
: base(null)
|
|
{
|
|
|
|
this.seed = seed;
|
|
this.Biome = biome;
|
|
this.Difficulty = difficulty;
|
|
this.generationParams = generationParams;
|
|
|
|
sizeFactor = MathHelper.Clamp(sizeFactor, 0.0f, 1.0f);
|
|
int width = (int)MathHelper.Lerp(generationParams.MinWidth, generationParams.MaxWidth, sizeFactor);
|
|
|
|
borders = new Rectangle(0, 0,
|
|
(width / GridCellSize) * GridCellSize,
|
|
(generationParams.Height / GridCellSize) * GridCellSize);
|
|
|
|
//remove from entity dictionary
|
|
base.Remove();
|
|
}
|
|
|
|
public static Level CreateRandom(LocationConnection locationConnection)
|
|
{
|
|
string seed = locationConnection.Locations[0].Name + locationConnection.Locations[1].Name;
|
|
|
|
float sizeFactor = MathUtils.InverseLerp(
|
|
MapGenerationParams.Instance.SmallLevelConnectionLength,
|
|
MapGenerationParams.Instance.LargeLevelConnectionLength,
|
|
locationConnection.Length);
|
|
|
|
return new Level(seed,
|
|
locationConnection.Difficulty,
|
|
sizeFactor,
|
|
LevelGenerationParams.GetRandom(seed, locationConnection.Biome),
|
|
locationConnection.Biome);
|
|
}
|
|
|
|
public static Level CreateRandom(string seed = "", float? difficulty = null, LevelGenerationParams generationParams = null)
|
|
{
|
|
if (seed == "")
|
|
{
|
|
seed = Rand.Range(0, int.MaxValue, Rand.RandSync.Server).ToString();
|
|
}
|
|
|
|
Rand.SetSyncedSeed(ToolBox.StringToInt(seed));
|
|
|
|
if (generationParams == null) generationParams = LevelGenerationParams.GetRandom(seed);
|
|
var biome = LevelGenerationParams.GetBiomes().Find(b => generationParams.AllowedBiomes.Contains(b));
|
|
|
|
return new Level(
|
|
seed,
|
|
difficulty ?? Rand.Range(30.0f, 80.0f, Rand.RandSync.Server),
|
|
Rand.Range(0.0f, 1.0f, Rand.RandSync.Server),
|
|
generationParams,
|
|
biome);
|
|
}
|
|
|
|
public void Generate(bool mirror)
|
|
{
|
|
if (loaded != null) loaded.Remove();
|
|
loaded = this;
|
|
|
|
levelObjectManager = new LevelObjectManager();
|
|
|
|
Mirrored = mirror;
|
|
|
|
#if CLIENT
|
|
if (backgroundCreatureManager == null)
|
|
{
|
|
var files = GameMain.Instance.GetFilesOfType(ContentType.BackgroundCreaturePrefabs);
|
|
if (files.Count() > 0)
|
|
backgroundCreatureManager = new BackgroundCreatureManager(files);
|
|
else
|
|
backgroundCreatureManager = new BackgroundCreatureManager("Content/BackgroundCreatures/BackgroundCreaturePrefabs.xml");
|
|
}
|
|
#endif
|
|
Stopwatch sw = new Stopwatch();
|
|
sw.Start();
|
|
|
|
positionsOfInterest = new List<InterestingPosition>();
|
|
extraWalls = new List<LevelWall>();
|
|
bodies = new List<Body>();
|
|
List<Vector2> sites = new List<Vector2>();
|
|
|
|
Voronoi voronoi = new Voronoi(1.0);
|
|
Rand.SetSyncedSeed(ToolBox.StringToInt(seed));
|
|
|
|
#if CLIENT
|
|
renderer = new LevelRenderer(this);
|
|
#endif
|
|
|
|
SeaFloorTopPos = generationParams.SeaFloorDepth + generationParams.MountainHeightMax + generationParams.SeaFloorVariance;
|
|
|
|
int minWidth = 6500;
|
|
int maxWidth = 50000;
|
|
if (Submarine.MainSub != null)
|
|
{
|
|
Rectangle dockedSubBorders = Submarine.MainSub.GetDockedBorders();
|
|
minWidth = Math.Max(minWidth, Math.Max(dockedSubBorders.Width, dockedSubBorders.Height));
|
|
minWidth = Math.Min(minWidth, maxWidth);
|
|
}
|
|
|
|
Rectangle pathBorders = borders;
|
|
pathBorders.Inflate(-minWidth * 2, -minWidth);
|
|
|
|
Debug.Assert(pathBorders.Width > 0 && pathBorders.Height > 0, "The size of the level's path area was negative.");
|
|
|
|
startPosition = new Point(
|
|
Rand.Range(minWidth, minWidth * 2, Rand.RandSync.Server),
|
|
Rand.Range(borders.Height / 2, borders.Height - minWidth * 2, Rand.RandSync.Server));
|
|
|
|
endPosition = new Point(
|
|
borders.Width - Rand.Range(minWidth, minWidth * 2, Rand.RandSync.Server),
|
|
Rand.Range(borders.Height / 2, borders.Height - minWidth * 2, Rand.RandSync.Server));
|
|
|
|
//----------------------------------------------------------------------------------
|
|
//generate the initial nodes for the main path and smaller tunnels
|
|
//----------------------------------------------------------------------------------
|
|
|
|
List<Point> pathNodes = new List<Point>();
|
|
pathNodes.Add(new Point(startPosition.X, borders.Height));
|
|
|
|
Point nodeInterval = generationParams.MainPathNodeIntervalRange;
|
|
|
|
for (int x = startPosition.X + nodeInterval.X;
|
|
x < endPosition.X - nodeInterval.X;
|
|
x += Rand.Range(nodeInterval.X, nodeInterval.Y, Rand.RandSync.Server))
|
|
{
|
|
pathNodes.Add(new Point(x, Rand.Range(pathBorders.Y, pathBorders.Bottom, Rand.RandSync.Server)));
|
|
}
|
|
|
|
pathNodes.Add(new Point(endPosition.X, borders.Height));
|
|
|
|
if (pathNodes.Count <= 2)
|
|
{
|
|
pathNodes.Insert(1, borders.Center);
|
|
}
|
|
|
|
GenerateTunnels(pathNodes, minWidth);
|
|
|
|
//----------------------------------------------------------------------------------
|
|
//generate voronoi sites
|
|
//----------------------------------------------------------------------------------
|
|
|
|
Point siteInterval = generationParams.VoronoiSiteInterval;
|
|
int siteIntervalSqr = (siteInterval.X * siteInterval.X + siteInterval.Y * siteInterval.Y);
|
|
Point siteVariance = generationParams.VoronoiSiteVariance;
|
|
List<double> siteCoordsX = new List<double>((borders.Height / siteInterval.Y) * (borders.Width / siteInterval.Y));
|
|
List<double> siteCoordsY = new List<double>((borders.Height / siteInterval.Y) * (borders.Width / siteInterval.Y));
|
|
for (int x = siteInterval.X / 2; x < borders.Width; x += siteInterval.X)
|
|
{
|
|
for (int y = siteInterval.Y / 2; y < borders.Height; y += siteInterval.Y)
|
|
{
|
|
int siteX = x + Rand.Range(-siteVariance.X, siteVariance.X, Rand.RandSync.Server);
|
|
int siteY = y + Rand.Range(-siteVariance.Y, siteVariance.Y, Rand.RandSync.Server);
|
|
|
|
if (smallTunnels.Any(t => t.Any(node => MathUtils.DistanceSquared(node.X, node.Y, siteX, siteY) < siteIntervalSqr)))
|
|
{
|
|
//add some more sites around the small tunnels to generate more small voronoi cells
|
|
if (x < borders.Width - siteInterval.X)
|
|
{
|
|
siteCoordsX.Add(x + siteInterval.X / 2);
|
|
siteCoordsY.Add(y);
|
|
}
|
|
if (y < borders.Height - siteInterval.Y)
|
|
{
|
|
siteCoordsX.Add(x);
|
|
siteCoordsY.Add(y + siteInterval.Y / 2);
|
|
}
|
|
if (x < borders.Width - siteInterval.X && y < borders.Height - siteInterval.Y)
|
|
{
|
|
siteCoordsX.Add(x + siteInterval.X / 2);
|
|
siteCoordsY.Add(y + siteInterval.Y / 2);
|
|
}
|
|
}
|
|
|
|
siteCoordsX.Add(siteX);
|
|
siteCoordsY.Add(siteY);
|
|
}
|
|
}
|
|
|
|
//----------------------------------------------------------------------------------
|
|
// construct the voronoi graph and cells
|
|
//----------------------------------------------------------------------------------
|
|
|
|
Stopwatch sw2 = new Stopwatch();
|
|
sw2.Start();
|
|
|
|
Debug.Assert(siteCoordsX.Count == siteCoordsY.Count);
|
|
List<GraphEdge> graphEdges = voronoi.MakeVoronoiGraph(siteCoordsX.ToArray(), siteCoordsY.ToArray(), borders.Width, borders.Height);
|
|
|
|
Debug.WriteLine("MakeVoronoiGraph: " + sw2.ElapsedMilliseconds + " ms");
|
|
sw2.Restart();
|
|
|
|
//construct voronoi cells based on the graph edges
|
|
cells = CaveGenerator.GraphEdgesToCells(graphEdges, borders, GridCellSize, out cellGrid);
|
|
|
|
Debug.WriteLine("find cells: " + sw2.ElapsedMilliseconds + " ms");
|
|
sw2.Restart();
|
|
|
|
//----------------------------------------------------------------------------------
|
|
// generate a path through the initial path nodes
|
|
//----------------------------------------------------------------------------------
|
|
|
|
List<VoronoiCell> mainPath = CaveGenerator.GeneratePath(pathNodes, cells, cellGrid, GridCellSize,
|
|
new Rectangle(pathBorders.X, pathBorders.Y, pathBorders.Width, borders.Height), 0.5f, false);
|
|
|
|
for (int i = 2; i < mainPath.Count; i += 3)
|
|
{
|
|
positionsOfInterest.Add(new InterestingPosition(
|
|
new Point((int)mainPath[i].Site.Coord.X, (int)mainPath[i].Site.Coord.Y),
|
|
PositionType.MainPath));
|
|
}
|
|
|
|
List<VoronoiCell> pathCells = new List<VoronoiCell>(mainPath);
|
|
|
|
//make sure the path is wide enough to pass through
|
|
EnlargeMainPath(pathCells, minWidth);
|
|
|
|
foreach (InterestingPosition positionOfInterest in positionsOfInterest)
|
|
{
|
|
WayPoint wayPoint = new WayPoint(
|
|
positionOfInterest.Position.ToVector2(),
|
|
SpawnType.Enemy,
|
|
submarine: null);
|
|
}
|
|
|
|
startPosition.X = (int)pathCells[0].Site.Coord.X;
|
|
|
|
//----------------------------------------------------------------------------------
|
|
// tunnels through the tunnel nodes
|
|
//----------------------------------------------------------------------------------
|
|
|
|
List<List<Point>> validTunnels = new List<List<Point>>();
|
|
foreach (List<Point> tunnel in smallTunnels)
|
|
{
|
|
if (tunnel.Count < 2) continue;
|
|
|
|
//find the cell which the path starts from
|
|
int startCellIndex = CaveGenerator.FindCellIndex(tunnel[0], cells, cellGrid, GridCellSize, 1);
|
|
if (startCellIndex < 0) continue;
|
|
|
|
//if it wasn't one of the cells in the main path, don't create a tunnel
|
|
if (cells[startCellIndex].CellType != CellType.Path) continue;
|
|
|
|
int mainPathCellCount = 0;
|
|
for (int j = 0; j < tunnel.Count; j++)
|
|
{
|
|
int tunnelCellIndex = CaveGenerator.FindCellIndex(tunnel[j], cells, cellGrid, GridCellSize, 1);
|
|
if (tunnelCellIndex > -1 && cells[tunnelCellIndex].CellType == CellType.Path) mainPathCellCount++;
|
|
}
|
|
if (mainPathCellCount > tunnel.Count / 2) continue;
|
|
|
|
var newPathCells = CaveGenerator.GeneratePath(tunnel, cells, cellGrid, GridCellSize, pathBorders);
|
|
positionsOfInterest.Add(new InterestingPosition(tunnel.Last(), PositionType.Cave));
|
|
if (tunnel.Count > 4) positionsOfInterest.Add(new InterestingPosition(tunnel[tunnel.Count / 2], PositionType.Cave));
|
|
validTunnels.Add(tunnel);
|
|
pathCells.AddRange(newPathCells);
|
|
}
|
|
smallTunnels = validTunnels;
|
|
|
|
sw2.Restart();
|
|
|
|
|
|
//----------------------------------------------------------------------------------
|
|
// remove unnecessary cells and create some holes at the bottom of the level
|
|
//----------------------------------------------------------------------------------
|
|
|
|
cells = CleanCells(pathCells);
|
|
|
|
int xPadding = borders.Width / 5;
|
|
int yPadding = borders.Height / 5;
|
|
pathCells.AddRange(CreateBottomHoles(generationParams.BottomHoleProbability, new Rectangle(
|
|
xPadding, 0,
|
|
borders.Width - xPadding * 2, borders.Height - yPadding)));
|
|
|
|
foreach (VoronoiCell cell in cells)
|
|
{
|
|
if (cell.Site.Coord.Y < borders.Height / 2) continue;
|
|
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);
|
|
|
|
cell.CellType = CellType.Path;
|
|
cells.Remove(cell);
|
|
}
|
|
|
|
for (int x = 0; x < cellGrid.GetLength(0); x++)
|
|
{
|
|
for (int y = 0; y < cellGrid.GetLength(1); y++)
|
|
{
|
|
cellGrid[x, y].Clear();
|
|
}
|
|
}
|
|
|
|
//----------------------------------------------------------------------------------
|
|
// mirror if needed
|
|
//----------------------------------------------------------------------------------
|
|
|
|
if (mirror)
|
|
{
|
|
HashSet<GraphEdge> mirroredEdges = new HashSet<GraphEdge>();
|
|
HashSet<Site> mirroredSites = new HashSet<Site>();
|
|
List<VoronoiCell> allCells = new List<VoronoiCell>(cells);
|
|
allCells.AddRange(pathCells);
|
|
foreach (VoronoiCell cell in allCells)
|
|
{
|
|
foreach (GraphEdge edge in cell.Edges)
|
|
{
|
|
if (mirroredEdges.Contains(edge)) continue;
|
|
edge.Point1.X = borders.Width - edge.Point1.X;
|
|
edge.Point2.X = borders.Width - edge.Point2.X;
|
|
if (!mirroredSites.Contains(edge.Site1))
|
|
{
|
|
//make sure that sites right at the edge of a grid cell end up in the same cell as in the non-mirrored level
|
|
if (edge.Site1.Coord.X % GridCellSize < 1.0f &&
|
|
edge.Site1.Coord.X % GridCellSize >= 0.0f) edge.Site1.Coord.X += 1.0f;
|
|
edge.Site1.Coord.X = borders.Width - edge.Site1.Coord.X;
|
|
mirroredSites.Add(edge.Site1);
|
|
}
|
|
if (!mirroredSites.Contains(edge.Site2))
|
|
{
|
|
if (edge.Site2.Coord.X % GridCellSize < 1.0f &&
|
|
edge.Site2.Coord.X % GridCellSize >= 0.0f) edge.Site2.Coord.X += 1.0f;
|
|
edge.Site2.Coord.X = borders.Width - edge.Site2.Coord.X;
|
|
mirroredSites.Add(edge.Site2);
|
|
}
|
|
mirroredEdges.Add(edge);
|
|
}
|
|
}
|
|
|
|
|
|
foreach (List<Point> smallTunnel in smallTunnels)
|
|
{
|
|
for (int i = 0; i < smallTunnel.Count; i++)
|
|
{
|
|
smallTunnel[i] = new Point(borders.Width - smallTunnel[i].X, smallTunnel[i].Y);
|
|
}
|
|
}
|
|
|
|
for (int i = 0; i < positionsOfInterest.Count; i++)
|
|
{
|
|
positionsOfInterest[i] = new InterestingPosition(
|
|
new Point(borders.Width - positionsOfInterest[i].Position.X, positionsOfInterest[i].Position.Y),
|
|
positionsOfInterest[i].PositionType);
|
|
}
|
|
|
|
foreach (WayPoint waypoint in WayPoint.WayPointList)
|
|
{
|
|
if (waypoint.Submarine != null) continue;
|
|
waypoint.Move(new Vector2((borders.Width / 2 - waypoint.Position.X) * 2, 0.0f));
|
|
}
|
|
|
|
startPosition.X = borders.Width - startPosition.X;
|
|
endPosition.X = borders.Width - endPosition.X;
|
|
}
|
|
|
|
foreach (VoronoiCell cell in cells)
|
|
{
|
|
int x = (int)Math.Floor(cell.Site.Coord.X / GridCellSize);
|
|
int y = (int)Math.Floor(cell.Site.Coord.Y / GridCellSize);
|
|
|
|
if (x < 0 || y < 0 || x >= cellGrid.GetLength(0) || y >= cellGrid.GetLength(1)) continue;
|
|
|
|
cellGrid[x, y].Add(cell);
|
|
}
|
|
|
|
//----------------------------------------------------------------------------------
|
|
// create some ruins
|
|
//----------------------------------------------------------------------------------
|
|
|
|
ruins = new List<Ruin>();
|
|
for (int i = 0; i < generationParams.RuinCount; i++)
|
|
{
|
|
GenerateRuin(mainPath, this, mirror);
|
|
}
|
|
|
|
|
|
//----------------------------------------------------------------------------------
|
|
// create floating ice chunks
|
|
//----------------------------------------------------------------------------------
|
|
|
|
if (generationParams.FloatingIceChunkCount > 0)
|
|
{
|
|
List<Point> iceChunkPositions = new List<Point>();
|
|
foreach (InterestingPosition pos in positionsOfInterest)
|
|
{
|
|
if (pos.PositionType != PositionType.MainPath || pos.Position.X < 5000 || pos.Position.X > Size.X - 5000) continue;
|
|
if (Math.Abs(pos.Position.X - StartPosition.X) < 10000) continue;
|
|
if (Math.Abs(pos.Position.Y - StartPosition.Y) < 10000) continue;
|
|
if (GetTooCloseCells(pos.Position.ToVector2(), minWidth * 0.7f).Count > 0) continue;
|
|
iceChunkPositions.Add(pos.Position);
|
|
}
|
|
|
|
for (int i = 0; i < generationParams.FloatingIceChunkCount; i++)
|
|
{
|
|
if (iceChunkPositions.Count == 0) break;
|
|
Point selectedPos = iceChunkPositions[Rand.Int(iceChunkPositions.Count, Rand.RandSync.Server)];
|
|
float chunkRadius = Rand.Range(500.0f, 1000.0f, Rand.RandSync.Server);
|
|
var newChunk = new LevelWall(CaveGenerator.CreateRandomChunk(chunkRadius, 8, chunkRadius * 0.8f), Color.White, this, true)
|
|
{
|
|
MoveSpeed = Rand.Range(100.0f, 200.0f, Rand.RandSync.Server),
|
|
MoveAmount = new Vector2(0.0f, minWidth * 0.7f)
|
|
};
|
|
newChunk.Body.Position = ConvertUnits.ToSimUnits(selectedPos.ToVector2());
|
|
newChunk.Body.BodyType = BodyType.Dynamic;
|
|
newChunk.Body.FixedRotation = true;
|
|
newChunk.Body.LinearDamping = 0.5f;
|
|
newChunk.Body.GravityScale = 0.0f;
|
|
newChunk.Body.Mass *= 10.0f;
|
|
extraWalls.Add(newChunk);
|
|
iceChunkPositions.Remove(selectedPos);
|
|
}
|
|
}
|
|
|
|
//----------------------------------------------------------------------------------
|
|
// generate the bodies and rendered triangles of the cells
|
|
//----------------------------------------------------------------------------------
|
|
|
|
startPosition.Y = borders.Height;
|
|
endPosition.Y = borders.Height;
|
|
|
|
foreach (VoronoiCell cell in cells)
|
|
{
|
|
foreach (GraphEdge ge in cell.Edges)
|
|
{
|
|
VoronoiCell adjacentCell = ge.AdjacentCell(cell);
|
|
ge.IsSolid = (adjacentCell == null || !cells.Contains(adjacentCell));
|
|
}
|
|
}
|
|
|
|
List<VoronoiCell> cellsWithBody = new List<VoronoiCell>(cells);
|
|
if (generationParams.CellRoundingAmount > 0.01f || generationParams.CellIrregularity > 0.01f)
|
|
{
|
|
foreach (VoronoiCell cell in cellsWithBody)
|
|
{
|
|
CaveGenerator.RoundCell(cell,
|
|
minEdgeLength: generationParams.CellSubdivisionLength,
|
|
roundingAmount: generationParams.CellRoundingAmount,
|
|
irregularity: generationParams.CellIrregularity);
|
|
}
|
|
}
|
|
|
|
bodies.Add(CaveGenerator.GeneratePolygons(cellsWithBody, this, out List<Vector2[]> triangles));
|
|
|
|
#if CLIENT
|
|
renderer.SetBodyVertices(CaveGenerator.GenerateRenderVerticeList(triangles).ToArray(), generationParams.WallColor);
|
|
renderer.SetWallVertices(CaveGenerator.GenerateWallShapes(cellsWithBody, this), generationParams.WallColor);
|
|
#endif
|
|
|
|
|
|
//----------------------------------------------------------------------------------
|
|
// create (placeholder) outposts at the start and end of the level
|
|
//----------------------------------------------------------------------------------
|
|
|
|
CreateOutposts();
|
|
|
|
//----------------------------------------------------------------------------------
|
|
// top barrier & sea floor
|
|
//----------------------------------------------------------------------------------
|
|
|
|
TopBarrier = BodyFactory.CreateEdge(GameMain.World,
|
|
ConvertUnits.ToSimUnits(new Vector2(borders.X, 0)),
|
|
ConvertUnits.ToSimUnits(new Vector2(borders.Right, 0)));
|
|
|
|
TopBarrier.SetTransform(ConvertUnits.ToSimUnits(new Vector2(0.0f, borders.Height)), 0.0f);
|
|
TopBarrier.BodyType = BodyType.Static;
|
|
TopBarrier.CollisionCategories = Physics.CollisionLevel;
|
|
|
|
bodies.Add(TopBarrier);
|
|
|
|
GenerateSeaFloor(mirror);
|
|
|
|
levelObjectManager.PlaceObjects(this, generationParams.LevelObjectAmount);
|
|
|
|
GenerateItems();
|
|
|
|
EqualityCheckVal = Rand.Int(int.MaxValue, Rand.RandSync.Server);
|
|
|
|
#if CLIENT
|
|
backgroundCreatureManager.SpawnSprites(80);
|
|
#endif
|
|
|
|
foreach (VoronoiCell cell in cells)
|
|
{
|
|
foreach (GraphEdge edge in cell.Edges)
|
|
{
|
|
edge.Cell1 = null;
|
|
edge.Cell2 = null;
|
|
edge.Site1 = null;
|
|
edge.Site2 = null;
|
|
}
|
|
}
|
|
|
|
//initialize MapEntities that aren't in any sub (e.g. items inside ruins)
|
|
MapEntity.MapLoaded(MapEntity.mapEntityList.FindAll(me => me.Submarine == null), false);
|
|
|
|
Debug.WriteLine("Generatelevel: " + sw2.ElapsedMilliseconds + " ms");
|
|
sw2.Restart();
|
|
|
|
if (mirror)
|
|
{
|
|
Point temp = startPosition;
|
|
startPosition = endPosition;
|
|
endPosition = temp;
|
|
}
|
|
if (StartOutpost != null)
|
|
{
|
|
startPosition = new Point((int)StartOutpost.WorldPosition.X, (int)StartOutpost.WorldPosition.Y);
|
|
}
|
|
if (EndOutpost != null)
|
|
{
|
|
endPosition = new Point((int)EndOutpost.WorldPosition.X, (int)EndOutpost.WorldPosition.Y);
|
|
}
|
|
|
|
Debug.WriteLine("**********************************************************************************");
|
|
Debug.WriteLine("Generated a map with " + siteCoordsX.Count + " sites in " + sw.ElapsedMilliseconds + " ms");
|
|
Debug.WriteLine("Seed: " + seed);
|
|
Debug.WriteLine("**********************************************************************************");
|
|
|
|
if (GameSettings.VerboseLogging)
|
|
{
|
|
DebugConsole.NewMessage("Generated level with the seed " + seed + " (type: " + generationParams.Name + ")", Color.White);
|
|
}
|
|
|
|
//assign an ID to make entity events work
|
|
ID = FindFreeID();
|
|
}
|
|
|
|
|
|
private List<VoronoiCell> CreateBottomHoles(float holeProbability, Rectangle limits)
|
|
{
|
|
List<VoronoiCell> toBeRemoved = new List<VoronoiCell>();
|
|
foreach (VoronoiCell cell in cells)
|
|
{
|
|
if (Rand.Range(0.0f, 1.0f, Rand.RandSync.Server) > holeProbability) continue;
|
|
|
|
if (!limits.Contains(cell.Site.Coord.X, cell.Site.Coord.Y)) 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;
|
|
}
|
|
|
|
private void EnlargeMainPath(List<VoronoiCell> pathCells, float minWidth)
|
|
{
|
|
List<WayPoint> wayPoints = new List<WayPoint>();
|
|
|
|
var newWaypoint = new WayPoint(new Rectangle((int)pathCells[0].Site.Coord.X, borders.Height, 10, 10), null);
|
|
wayPoints.Add(newWaypoint);
|
|
|
|
for (int i = 0; i < pathCells.Count; i++)
|
|
{
|
|
pathCells[i].CellType = CellType.Path;
|
|
|
|
newWaypoint = new WayPoint(new Rectangle((int)pathCells[i].Site.Coord.X, (int)pathCells[i].Center.Y, 10, 10), null);
|
|
wayPoints.Add(newWaypoint);
|
|
|
|
wayPoints[wayPoints.Count-2].linkedTo.Add(newWaypoint);
|
|
newWaypoint.linkedTo.Add(wayPoints[wayPoints.Count - 2]);
|
|
|
|
for (int n = 0; n < wayPoints.Count; n++)
|
|
{
|
|
if (wayPoints[n].Position != newWaypoint.Position) continue;
|
|
|
|
wayPoints[n].linkedTo.Add(newWaypoint);
|
|
newWaypoint.linkedTo.Add(wayPoints[n]);
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
newWaypoint = new WayPoint(new Rectangle((int)pathCells[pathCells.Count - 1].Site.Coord.X, borders.Height, 10, 10), null);
|
|
wayPoints.Add(newWaypoint);
|
|
|
|
wayPoints[wayPoints.Count - 2].linkedTo.Add(newWaypoint);
|
|
newWaypoint.linkedTo.Add(wayPoints[wayPoints.Count - 2]);
|
|
|
|
if (minWidth > 0.0f)
|
|
{
|
|
List<VoronoiCell> removedCells = GetTooCloseCells(pathCells, minWidth);
|
|
foreach (VoronoiCell removedCell in removedCells)
|
|
{
|
|
if (removedCell.CellType == CellType.Path) continue;
|
|
|
|
pathCells.Add(removedCell);
|
|
removedCell.CellType = CellType.Path;
|
|
}
|
|
}
|
|
}
|
|
|
|
private List<VoronoiCell> GetTooCloseCells(List<VoronoiCell> emptyCells, float minDistance)
|
|
{
|
|
List<VoronoiCell> tooCloseCells = new List<VoronoiCell>();
|
|
|
|
Vector2 position = emptyCells[0].Center;
|
|
|
|
if (minDistance <= 0.0f) return tooCloseCells;
|
|
|
|
float step = 100.0f;
|
|
int targetCellIndex = 1;
|
|
|
|
minDistance *= 0.5f;
|
|
do
|
|
{
|
|
tooCloseCells.AddRange(GetTooCloseCells(position, minDistance));
|
|
|
|
position += Vector2.Normalize(emptyCells[targetCellIndex].Center - position) * step;
|
|
|
|
if (Vector2.Distance(emptyCells[targetCellIndex].Center, position) < step * 2.0f) targetCellIndex++;
|
|
|
|
} while (Vector2.Distance(position, emptyCells[emptyCells.Count - 1].Center) > step * 2.0f);
|
|
|
|
return tooCloseCells;
|
|
}
|
|
|
|
private List<VoronoiCell> GetTooCloseCells(Vector2 position, float minDistance)
|
|
{
|
|
List<VoronoiCell> tooCloseCells = new List<VoronoiCell>();
|
|
|
|
var closeCells = GetCells(position, 3);
|
|
|
|
float minDistSqr = minDistance * minDistance;
|
|
foreach (VoronoiCell cell in closeCells)
|
|
{
|
|
bool tooClose = false;
|
|
foreach (GraphEdge edge in cell.Edges)
|
|
{
|
|
if (Vector2.DistanceSquared(edge.Point1, position) < minDistSqr ||
|
|
Vector2.DistanceSquared(edge.Point2, position) < minDistSqr)
|
|
{
|
|
tooClose = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (tooClose && !tooCloseCells.Contains(cell)) tooCloseCells.Add(cell);
|
|
}
|
|
|
|
return tooCloseCells;
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// remove all cells except those that are adjacent to the empty cells
|
|
/// </summary>
|
|
private List<VoronoiCell> CleanCells(List<VoronoiCell> emptyCells)
|
|
{
|
|
List<VoronoiCell> newCells = new List<VoronoiCell>();
|
|
|
|
foreach (VoronoiCell cell in emptyCells)
|
|
{
|
|
foreach (GraphEdge edge in cell.Edges)
|
|
{
|
|
VoronoiCell adjacent = edge.AdjacentCell(cell);
|
|
if (adjacent != null && !newCells.Contains(adjacent))
|
|
{
|
|
newCells.Add(adjacent);
|
|
}
|
|
}
|
|
}
|
|
|
|
return newCells;
|
|
}
|
|
|
|
private void GenerateSeaFloor(bool mirror)
|
|
{
|
|
BottomPos = generationParams.SeaFloorDepth;
|
|
SeaFloorTopPos = BottomPos;
|
|
|
|
bottomPositions = new List<Point>
|
|
{
|
|
new Point(0, BottomPos)
|
|
};
|
|
|
|
int mountainCount = Rand.Range(generationParams.MountainCountMin, generationParams.MountainCountMax, Rand.RandSync.Server);
|
|
for (int i = 0; i < mountainCount; i++)
|
|
{
|
|
bottomPositions.Add(
|
|
new Point(Size.X / (mountainCount + 1) * (i + 1),
|
|
BottomPos + Rand.Range(generationParams.MountainHeightMin, generationParams.MountainHeightMax, Rand.RandSync.Server)));
|
|
}
|
|
bottomPositions.Add(new Point(Size.X, BottomPos));
|
|
|
|
int minVertexInterval = 5000;
|
|
float currInverval = Size.X / 2;
|
|
while (currInverval > minVertexInterval)
|
|
{
|
|
for (int i = 0; i < bottomPositions.Count - 1; i++)
|
|
{
|
|
bottomPositions.Insert(i + 1,
|
|
new Point(
|
|
(bottomPositions[i].X + bottomPositions[i + 1].X) / 2,
|
|
(bottomPositions[i].Y + bottomPositions[i + 1].Y) / 2 + Rand.Range(0, generationParams.SeaFloorVariance, Rand.RandSync.Server)));
|
|
i++;
|
|
}
|
|
|
|
currInverval /= 2;
|
|
}
|
|
|
|
if (mirror)
|
|
{
|
|
for (int i = 0; i < bottomPositions.Count; i++)
|
|
{
|
|
bottomPositions[i] = new Point(borders.Size.X - bottomPositions[i].X, bottomPositions[i].Y);
|
|
}
|
|
}
|
|
|
|
SeaFloorTopPos = bottomPositions.Max(p => p.Y);
|
|
seaFloor = new LevelWall(bottomPositions.Select(p => p.ToVector2()).ToList(), new Vector2(0.0f, -2000.0f), generationParams.WallColor, this);
|
|
extraWalls.Add(seaFloor);
|
|
|
|
BottomBarrier = BodyFactory.CreateEdge(GameMain.World,
|
|
ConvertUnits.ToSimUnits(new Vector2(borders.X, 0)),
|
|
ConvertUnits.ToSimUnits(new Vector2(borders.Right, 0)));
|
|
|
|
BottomBarrier.SetTransform(ConvertUnits.ToSimUnits(new Vector2(0.0f, BottomPos)), 0.0f);
|
|
BottomBarrier.BodyType = BodyType.Static;
|
|
BottomBarrier.CollisionCategories = Physics.CollisionLevel;
|
|
|
|
bodies.Add(BottomBarrier);
|
|
}
|
|
|
|
private void GenerateTunnels(List<Point> pathNodes, int pathWidth)
|
|
{
|
|
smallTunnels = new List<List<Point>>();
|
|
for (int i = 0; i < generationParams.SmallTunnelCount; i++)
|
|
{
|
|
var tunnelStartPos = pathNodes[Rand.Range(1, pathNodes.Count - 2, Rand.RandSync.Server)];
|
|
|
|
int tunnelLength = Rand.Range(
|
|
generationParams.SmallTunnelLengthRange.X,
|
|
generationParams.SmallTunnelLengthRange.Y,
|
|
Rand.RandSync.Server);
|
|
|
|
List<Point> tunnelNodes = new List<Point>()
|
|
{
|
|
tunnelStartPos,
|
|
tunnelStartPos + new Point(0, Math.Sign(tunnelStartPos.Y - Size.Y / 2) * pathWidth * 2)
|
|
};
|
|
|
|
List<Point> 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<Point> branch = GenerateTunnel(
|
|
new List<Point>() { 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<Point> GenerateTunnel(List<Point> tunnelNodes, float tunnelLength, List<Point> avoidNodes)
|
|
{
|
|
int sectionLength = 1000;
|
|
|
|
float currLength = 0.0f;
|
|
DoubleVector2 dir = null;
|
|
while (currLength < tunnelLength)
|
|
{
|
|
var prevDir = dir;
|
|
dir = Rand.Vector(1.0, Rand.RandSync.Server);
|
|
|
|
dir.Y += Math.Sign(tunnelNodes[tunnelNodes.Count - 1].Y - Size.Y / 2) * 0.5f;
|
|
if (prevDir != null)
|
|
{
|
|
dir.X = (dir.X + prevDir.X) / 2.0;
|
|
dir.Y = (dir.Y + prevDir.Y) / 2.0;
|
|
}
|
|
|
|
double avoidDist = 20000;
|
|
double avoidDistSqr = avoidDist * avoidDist;
|
|
foreach (Point pathNode in avoidNodes)
|
|
{
|
|
double diffX = tunnelNodes[tunnelNodes.Count - 1].X - pathNode.X;
|
|
double diffY = tunnelNodes[tunnelNodes.Count - 1].Y - pathNode.Y;
|
|
if (Math.Abs(diffX) < 1.0f || Math.Abs(diffY) < 1.0f) continue;
|
|
|
|
double distSqr = (diffX * diffX + diffY * diffY);
|
|
Debug.Assert(distSqr > 0);
|
|
if (distSqr < avoidDistSqr)
|
|
{
|
|
double dist = Math.Sqrt(distSqr);
|
|
|
|
dir.X += (diffX / dist) * (1.0f - dist / avoidDist);
|
|
dir.Y += (diffY / dist) * (1.0f - dist / avoidDist);
|
|
}
|
|
}
|
|
|
|
dir.Normalize();
|
|
|
|
if (tunnelNodes.Last().Y + dir.Y > Size.Y)
|
|
{
|
|
//head back down if the tunnel has reached the top of the level
|
|
dir.Y = -dir.Y;
|
|
}
|
|
else if (tunnelNodes.Last().Y + dir.Y * 500 < 500)
|
|
{
|
|
//head back up if reached the bottom of the level
|
|
dir.Y = -dir.Y;
|
|
}
|
|
else if (tunnelNodes.Last().Y + dir.Y + dir.Y < 0.0f ||
|
|
tunnelNodes.Last().Y + dir.Y + dir.Y < SeaFloorTopPos)
|
|
{
|
|
//head back up if reached the sea floor
|
|
dir.Y = -dir.Y;
|
|
}
|
|
|
|
Point nextNode = tunnelNodes.Last() + new Point((int)(dir.X * sectionLength), (int)(dir.Y * sectionLength));
|
|
nextNode.X = MathHelper.Clamp(nextNode.X, 500, Size.X - 500);
|
|
nextNode.Y = MathHelper.Clamp(nextNode.Y, SeaFloorTopPos, Size.Y - 500);
|
|
tunnelNodes.Add(nextNode);
|
|
currLength += sectionLength;
|
|
}
|
|
|
|
return tunnelNodes;
|
|
}
|
|
|
|
private void GenerateRuin(List<VoronoiCell> mainPath, Level level, bool mirror)
|
|
{
|
|
var ruinGenerationParams = RuinGenerationParams.GetRandom();
|
|
|
|
Point ruinSize = new Point(
|
|
Rand.Range(ruinGenerationParams.SizeMin.X, ruinGenerationParams.SizeMax.X, Rand.RandSync.Server),
|
|
Rand.Range(ruinGenerationParams.SizeMin.Y, ruinGenerationParams.SizeMax.Y, Rand.RandSync.Server));
|
|
int ruinRadius = Math.Max(ruinSize.X, ruinSize.Y) / 2;
|
|
|
|
int cellIndex = Rand.Int(cells.Count, Rand.RandSync.Server);
|
|
Point ruinPos = new Point((int)cells[cellIndex].Site.Coord.X, (int)cells[cellIndex].Site.Coord.X);
|
|
|
|
//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, 0.0f, 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);
|
|
|
|
double minDist = ruinRadius * 2;
|
|
double minDistSqr = minDist * minDist;
|
|
|
|
int iter = 0;
|
|
while (mainPath.Any(p => MathUtils.DistanceSquared(ruinPos.X, ruinPos.Y, p.Site.Coord.X, p.Site.Coord.Y) < minDistSqr) ||
|
|
ruins.Any(r => r.Area.Intersects(new Rectangle(ruinPos - new Point(ruinSize.X / 2, ruinSize.Y / 2), ruinSize))))
|
|
{
|
|
double weighedPathPosX = ruinPos.X;
|
|
double weighedPathPosY = ruinPos.Y;
|
|
iter++;
|
|
|
|
foreach (VoronoiCell pathCell in mainPath)
|
|
{
|
|
double diffX = ruinPos.X - pathCell.Site.Coord.X;
|
|
double diffY = ruinPos.Y - pathCell.Site.Coord.Y;
|
|
|
|
double distSqr = diffX * diffX + diffY * diffY;
|
|
if (distSqr < 1.0)
|
|
{
|
|
diffX = 0;
|
|
diffY = 1;
|
|
distSqr = 1.0;
|
|
}
|
|
if (distSqr > 10000.0 * 10000.0) continue;
|
|
|
|
double dist = Math.Sqrt(distSqr);
|
|
double moveAmountX = 100.0 * diffX / dist;
|
|
double moveAmountY = 100.0 * diffY / dist;
|
|
|
|
weighedPathPosX += moveAmountX;
|
|
weighedPathPosY += moveAmountY;
|
|
weighedPathPosY = Math.Min(borders.Y + borders.Height - ruinSize.Y / 2, weighedPathPosY);
|
|
}
|
|
|
|
Rectangle ruinArea = new Rectangle(ruinPos - new Point(ruinSize.X / 2, ruinSize.Y / 2), ruinSize);
|
|
foreach (Ruin otherRuin in ruins)
|
|
{
|
|
if (!otherRuin.Area.Intersects(ruinArea)) continue;
|
|
|
|
double diffX = ruinArea.Center.X - otherRuin.Area.Center.X;
|
|
double diffY = ruinArea.Center.Y - otherRuin.Area.Center.Y;
|
|
|
|
double distSqr = diffX * diffX + diffY * diffY;
|
|
if (distSqr < 0.01f)
|
|
{
|
|
diffX = 0;
|
|
diffY = -1;
|
|
distSqr = 1;
|
|
}
|
|
|
|
double dist = Math.Sqrt(distSqr);
|
|
double moveAmountX = diffX / dist;
|
|
double moveAmountY = diffY / dist;
|
|
|
|
int move = (Math.Max(ruinArea.Width, ruinArea.Height) + Math.Max(otherRuin.Area.Width, otherRuin.Area.Height)) / 2;
|
|
moveAmountX *= move;
|
|
moveAmountY *= move;
|
|
|
|
weighedPathPosX += moveAmountX;
|
|
weighedPathPosY += moveAmountY;
|
|
}
|
|
ruinPos = new Point((int)weighedPathPosX, (int)weighedPathPosY);
|
|
|
|
//if we can't find a suitable position after 10 000 iterations, give up
|
|
if (iter > 10000)
|
|
{
|
|
if (ruins.Count > 0)
|
|
{
|
|
//we already have some ruins, don't add this one at all
|
|
return;
|
|
}
|
|
string errorMsg = "Failed to find a suitable position for ruins. Level seed: " + seed +
|
|
", ruin size: " + ruinSize + ", selected sub " + (Submarine.MainSub == null ? "none" : Submarine.MainSub.Name);
|
|
DebugConsole.ThrowError(errorMsg);
|
|
GameAnalyticsManager.AddErrorEventOnce("Level.GenerateRuins:PosNotFound", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg);
|
|
break;
|
|
}
|
|
//if we haven't found a position after 500 iterations, try another starting point
|
|
else if (iter > 500 && iter % 500 == 0)
|
|
{
|
|
int newCellIndex = Rand.Int(cells.Count, Rand.RandSync.Server);
|
|
ruinPos = new Point((int)cells[newCellIndex].Site.Coord.X, (int)cells[newCellIndex].Site.Coord.X);
|
|
}
|
|
ruinPos.Y = Math.Min(ruinPos.Y, borders.Y + borders.Height - ruinSize.Y / 2);
|
|
ruinPos.Y = Math.Max(ruinPos.Y, SeaFloorTopPos + ruinSize.Y / 2);
|
|
}
|
|
|
|
if (Math.Abs(ruinPos.X) > int.MaxValue / 2 || Math.Abs(ruinPos.Y) > int.MaxValue / 2)
|
|
{
|
|
DebugConsole.ThrowError("Something went wrong during ruin generation. Ruin position: " + ruinPos);
|
|
return;
|
|
}
|
|
|
|
VoronoiCell closestPathCell = null;
|
|
double closestDist = 0.0f;
|
|
foreach (VoronoiCell pathCell in mainPath)
|
|
{
|
|
double dist = MathUtils.DistanceSquared(pathCell.Site.Coord.X, pathCell.Site.Coord.Y, ruinPos.X, ruinPos.Y);
|
|
if (closestPathCell == null || dist < closestDist)
|
|
{
|
|
closestPathCell = pathCell;
|
|
closestDist = dist;
|
|
}
|
|
}
|
|
|
|
var ruin = new Ruin(closestPathCell, cells, ruinGenerationParams, new Rectangle(ruinPos - new Point(ruinSize.X / 2, ruinSize.Y / 2), ruinSize), mirror);
|
|
ruins.Add(ruin);
|
|
|
|
ruin.RuinShapes.Sort((shape1, shape2) => shape2.DistanceFromEntrance.CompareTo(shape1.DistanceFromEntrance));
|
|
int waypointCount = 0;
|
|
foreach (WayPoint wp in WayPoint.WayPointList)
|
|
{
|
|
if (wp.SpawnType != SpawnType.Enemy || wp.Submarine != null) { continue; }
|
|
if (ruin.RuinShapes.Any(rs => rs.Rect.Contains(wp.WorldPosition)))
|
|
{
|
|
positionsOfInterest.Add(new InterestingPosition(new Point((int)wp.WorldPosition.X, (int)wp.WorldPosition.Y), PositionType.Ruin));
|
|
waypointCount++;
|
|
}
|
|
}
|
|
|
|
//not enough waypoints inside ruins -> create some spawn positions manually
|
|
for (int i = 0; i < 4 - waypointCount && i < ruin.RuinShapes.Count; i++)
|
|
{
|
|
positionsOfInterest.Add(new InterestingPosition(ruin.RuinShapes[i].Rect.Center, PositionType.Ruin));
|
|
}
|
|
|
|
foreach (RuinShape ruinShape in ruin.RuinShapes)
|
|
{
|
|
var tooClose = GetTooCloseCells(ruinShape.Rect.Center.ToVector2(), Math.Max(ruinShape.Rect.Width, ruinShape.Rect.Height));
|
|
|
|
foreach (VoronoiCell cell in tooClose)
|
|
{
|
|
if (cell.CellType == CellType.Empty) continue;
|
|
foreach (GraphEdge e in cell.Edges)
|
|
{
|
|
Rectangle rect = ruinShape.Rect;
|
|
rect.Y += rect.Height;
|
|
if (ruinShape.Rect.Contains(e.Point1) || ruinShape.Rect.Contains(e.Point2) ||
|
|
MathUtils.GetLineRectangleIntersection(e.Point1, e.Point2, rect, out _))
|
|
{
|
|
cell.CellType = CellType.Removed;
|
|
int x = (int)Math.Floor(cell.Center.X / GridCellSize);
|
|
int y = (int)Math.Floor(cell.Center.Y / GridCellSize);
|
|
cellGrid[x, y].Remove(cell);
|
|
cells.Remove(cell);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//cast a ray from the closest path cell towards the ruin and remove the cell it hits
|
|
//to ensure that there's always at least one way from the main tunnel to the ruin
|
|
List<VoronoiCell> validCells = cells.FindAll(c => c.CellType != CellType.Empty && c.CellType != CellType.Removed);
|
|
foreach (VoronoiCell cell in validCells)
|
|
{
|
|
foreach (GraphEdge e in cell.Edges)
|
|
{
|
|
if (MathUtils.LinesIntersect(closestPathCell.Center, ruinPos.ToVector2(), e.Point1, e.Point2))
|
|
{
|
|
cell.CellType = CellType.Removed;
|
|
int x = (int)Math.Floor(cell.Center.X / GridCellSize);
|
|
int y = (int)Math.Floor(cell.Center.Y / GridCellSize);
|
|
cellGrid[x, y].Remove(cell);
|
|
cells.Remove(cell);
|
|
break;
|
|
}
|
|
}
|
|
if (cell.CellType == CellType.Removed)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
private void GenerateItems()
|
|
{
|
|
string levelName = generationParams.Name.ToLowerInvariant();
|
|
List<Pair<ItemPrefab, float>> levelItems = new List<Pair<ItemPrefab, float>>();
|
|
foreach (MapEntityPrefab mapEntityPrefab in MapEntityPrefab.List)
|
|
{
|
|
if (!(mapEntityPrefab is ItemPrefab itemPrefab)) { continue; }
|
|
|
|
if (itemPrefab.LevelCommonness.TryGetValue(levelName, out float commonness) ||
|
|
itemPrefab.LevelCommonness.TryGetValue("", out commonness))
|
|
{
|
|
levelItems.Add(new Pair<ItemPrefab, float>(itemPrefab, commonness));
|
|
}
|
|
}
|
|
|
|
for (int i = 0; i < generationParams.ItemCount; i++)
|
|
{
|
|
var selectedPrefab = ToolBox.SelectWeightedRandom(
|
|
levelItems.Select(it => it.First).ToList(),
|
|
levelItems.Select(it => it.Second).ToList(),
|
|
Rand.RandSync.Server);
|
|
if (selectedPrefab == null) { break; }
|
|
|
|
var selectedCell = cells[Rand.Int(cells.Count, Rand.RandSync.Server)];
|
|
var selectedEdge = selectedCell.Edges.GetRandom(e => e.IsSolid && !e.OutsideLevel, Rand.RandSync.Server);
|
|
if (selectedEdge == null) continue;
|
|
|
|
float edgePos = Rand.Range(0.0f, 1.0f, Rand.RandSync.Server);
|
|
Vector2 selectedPos = Vector2.Lerp(selectedEdge.Point1, selectedEdge.Point2, edgePos);
|
|
Vector2 edgeNormal = selectedEdge.GetNormal(selectedCell);
|
|
|
|
var item = new Item(selectedPrefab, selectedPos, submarine: null);
|
|
item.Move(edgeNormal * item.Rect.Height / 2);
|
|
|
|
var holdable = item.GetComponent<Holdable>();
|
|
if (holdable == null)
|
|
{
|
|
DebugConsole.ThrowError("Error while placing items in the level - item \"" + item.Name + "\" is not holdable and cannot be attached to the level walls.");
|
|
}
|
|
else
|
|
{
|
|
holdable.AttachToWall();
|
|
#if CLIENT
|
|
item.SpriteRotation = -MathUtils.VectorToAngle(edgeNormal) + MathHelper.PiOver2;
|
|
#endif
|
|
}
|
|
}
|
|
}
|
|
|
|
public Vector2 GetRandomItemPos(PositionType spawnPosType, float randomSpread, float minDistFromSubs, float offsetFromWall = 10.0f)
|
|
{
|
|
if (!positionsOfInterest.Any())
|
|
{
|
|
return new Vector2(Size.X / 2, Size.Y / 2);
|
|
}
|
|
|
|
Vector2 position = Vector2.Zero;
|
|
|
|
offsetFromWall = ConvertUnits.ToSimUnits(offsetFromWall);
|
|
|
|
int tries = 0;
|
|
do
|
|
{
|
|
Loaded.TryGetInterestingPosition(true, spawnPosType, minDistFromSubs, out Vector2 startPos);
|
|
|
|
startPos += Rand.Vector(Rand.Range(0.0f, randomSpread, Rand.RandSync.Server), Rand.RandSync.Server);
|
|
|
|
Vector2 endPos = startPos - Vector2.UnitY * Size.Y;
|
|
|
|
if (Submarine.PickBody(
|
|
ConvertUnits.ToSimUnits(startPos),
|
|
ConvertUnits.ToSimUnits(endPos),
|
|
null, Physics.CollisionLevel | Physics.CollisionWall) != null)
|
|
{
|
|
position = ConvertUnits.ToDisplayUnits(Submarine.LastPickedPosition) + Vector2.Normalize(startPos - endPos) * offsetFromWall;
|
|
break;
|
|
}
|
|
|
|
tries++;
|
|
|
|
if (tries == 10)
|
|
{
|
|
position = EndPosition - Vector2.UnitY * 300.0f;
|
|
}
|
|
|
|
} while (tries < 10);
|
|
|
|
return position;
|
|
}
|
|
|
|
|
|
public bool TryGetInterestingPosition(bool useSyncedRand, PositionType positionType, float minDistFromSubs, out Vector2 position)
|
|
{
|
|
bool success = TryGetInterestingPosition(useSyncedRand, positionType, minDistFromSubs, out Point pos);
|
|
position = pos.ToVector2();
|
|
return success;
|
|
}
|
|
|
|
public bool TryGetInterestingPosition(bool useSyncedRand, PositionType positionType, float minDistFromSubs, out Point position)
|
|
{
|
|
if (!positionsOfInterest.Any())
|
|
{
|
|
position = new Point(Size.X / 2, Size.Y / 2);
|
|
return false;
|
|
}
|
|
|
|
var matchingPositions = positionsOfInterest.FindAll(p => positionType.HasFlag(p.PositionType));
|
|
|
|
if (minDistFromSubs > 0.0f)
|
|
{
|
|
foreach (Submarine sub in Submarine.Loaded)
|
|
{
|
|
matchingPositions.RemoveAll(p => Vector2.DistanceSquared(p.Position.ToVector2(), sub.WorldPosition) < minDistFromSubs * minDistFromSubs);
|
|
}
|
|
}
|
|
|
|
if (!matchingPositions.Any())
|
|
{
|
|
#if DEBUG
|
|
DebugConsole.ThrowError("Could not find a suitable position of interest. (PositionType: " + positionType + ", minDistFromSubs: " + minDistFromSubs + "\n" + Environment.StackTrace);
|
|
#endif
|
|
|
|
position = positionsOfInterest[Rand.Int(positionsOfInterest.Count, (useSyncedRand ? Rand.RandSync.Server : Rand.RandSync.Unsynced))].Position;
|
|
return false;
|
|
}
|
|
|
|
position = matchingPositions[Rand.Int(matchingPositions.Count, (useSyncedRand ? Rand.RandSync.Server : Rand.RandSync.Unsynced))].Position;
|
|
return true;
|
|
}
|
|
|
|
public void Update(float deltaTime, Camera cam)
|
|
{
|
|
levelObjectManager.Update(deltaTime);
|
|
|
|
foreach (LevelWall wall in ExtraWalls)
|
|
{
|
|
wall.Update(deltaTime);
|
|
}
|
|
|
|
if (GameMain.Server != null)
|
|
{
|
|
networkUpdateTimer += deltaTime;
|
|
if (networkUpdateTimer > NetworkUpdateInterval)
|
|
{
|
|
if (extraWalls.Any(w => w.Body.BodyType != BodyType.Static))
|
|
{
|
|
GameMain.Server.CreateEntityEvent(this);
|
|
}
|
|
networkUpdateTimer = 0.0f;
|
|
}
|
|
}
|
|
|
|
#if CLIENT
|
|
backgroundCreatureManager.Update(deltaTime, cam);
|
|
WaterRenderer.Instance?.ScrollWater(Vector2.UnitY, (float)deltaTime);
|
|
renderer.Update(deltaTime, cam);
|
|
#endif
|
|
}
|
|
|
|
public Vector2 GetBottomPosition(float xPosition)
|
|
{
|
|
int index = (int)Math.Floor(xPosition / Size.X * (bottomPositions.Count - 1));
|
|
if (index < 0 || index >= bottomPositions.Count - 1) return new Vector2(xPosition, BottomPos);
|
|
|
|
float yPos = MathHelper.Lerp(
|
|
bottomPositions[index].Y,
|
|
bottomPositions[index + 1].Y,
|
|
(xPosition - bottomPositions[index].X) / (bottomPositions[index + 1].X - bottomPositions[index].X));
|
|
|
|
return new Vector2(xPosition, yPos);
|
|
}
|
|
|
|
public List<VoronoiCell> GetAllCells()
|
|
{
|
|
List<VoronoiCell> cells = new List<VoronoiCell>();
|
|
for (int x = 0; x < cellGrid.GetLength(0); x++)
|
|
{
|
|
for (int y = 0; y < cellGrid.GetLength(1); y++)
|
|
{
|
|
cells.AddRange(cellGrid[x, y]);
|
|
}
|
|
}
|
|
return cells;
|
|
}
|
|
|
|
public List<VoronoiCell> GetCells(Vector2 worldPos, int searchDepth = 2)
|
|
{
|
|
int gridPosX = (int)Math.Floor(worldPos.X / GridCellSize);
|
|
int gridPosY = (int)Math.Floor(worldPos.Y / GridCellSize);
|
|
|
|
int startX = Math.Max(gridPosX - searchDepth, 0);
|
|
int endX = Math.Min(gridPosX + searchDepth, cellGrid.GetLength(0) - 1);
|
|
|
|
int startY = Math.Max(gridPosY - searchDepth, 0);
|
|
int endY = Math.Min(gridPosY + searchDepth, cellGrid.GetLength(1) - 1);
|
|
|
|
List<VoronoiCell> cells = new List<VoronoiCell>();
|
|
for (int y = startY; y <= endY; y++)
|
|
{
|
|
for (int x = startX; x <= endX; x++)
|
|
{
|
|
cells.AddRange(cellGrid[x, y]);
|
|
}
|
|
}
|
|
|
|
foreach (LevelWall wall in extraWalls)
|
|
{
|
|
foreach (VoronoiCell cell in wall.Cells)
|
|
{
|
|
cells.Add(cell);
|
|
}
|
|
}
|
|
|
|
return cells;
|
|
}
|
|
|
|
private void CreateOutposts()
|
|
{
|
|
var outpostFiles = ContentPackage.GetFilesOfType(GameMain.Config.SelectedContentPackages, ContentType.Outpost);
|
|
if (outpostFiles.Count() == 0)
|
|
{
|
|
DebugConsole.ThrowError("No outpost files found in the selected content packages");
|
|
return;
|
|
}
|
|
for (int i = 0; i < 2; i++)
|
|
{
|
|
//no outposts at either side of the level when there's more than one main sub (combat missions)
|
|
if (Submarine.MainSubs.Length > 1 && Submarine.MainSubs[0] != null && Submarine.MainSubs[1] != null)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
//only create a starting outpost in campaign mode
|
|
if (GameMain.GameSession?.GameMode as CampaignMode == null && ((i == 0) == !Mirrored))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
string outpostFile = outpostFiles.GetRandom(Rand.RandSync.Server);
|
|
var outpost = new Submarine(outpostFile, tryLoad: false);
|
|
outpost.Load(unloadPrevious: false);
|
|
outpost.MakeOutpost();
|
|
|
|
Point? minSize = null;
|
|
if (Submarine.MainSub != null)
|
|
{
|
|
Point subSize = Submarine.MainSub.GetDockedBorders().Size;
|
|
Point outpostSize = outpost.GetDockedBorders().Size;
|
|
minSize = new Point(Math.Max(subSize.X, outpostSize.X), subSize.Y + outpostSize.Y);
|
|
}
|
|
|
|
outpost.SetPosition(outpost.FindSpawnPos(i == 0 ? StartPosition : EndPosition, minSize));
|
|
if ((i == 0) == !Mirrored)
|
|
{
|
|
StartOutpost = outpost;
|
|
}
|
|
else
|
|
{
|
|
EndOutpost = outpost;
|
|
}
|
|
}
|
|
}
|
|
|
|
public override void Remove()
|
|
{
|
|
base.Remove();
|
|
#if CLIENT
|
|
if (renderer != null)
|
|
{
|
|
renderer.Dispose();
|
|
renderer = null;
|
|
}
|
|
#endif
|
|
|
|
if (levelObjectManager != null)
|
|
{
|
|
levelObjectManager.Remove();
|
|
levelObjectManager = null;
|
|
}
|
|
|
|
if (ruins != null)
|
|
{
|
|
ruins.Clear();
|
|
ruins = null;
|
|
}
|
|
|
|
if (extraWalls != null)
|
|
{
|
|
foreach (LevelWall w in extraWalls)
|
|
{
|
|
w.Dispose();
|
|
}
|
|
|
|
extraWalls = null;
|
|
}
|
|
|
|
cells = null;
|
|
|
|
if (bodies != null)
|
|
{
|
|
bodies.Clear();
|
|
bodies = null;
|
|
}
|
|
|
|
loaded = null;
|
|
}
|
|
|
|
public void ServerWrite(NetBuffer msg, Client c, object[] extraData = null)
|
|
{
|
|
foreach (LevelWall levelWall in extraWalls)
|
|
{
|
|
if (levelWall.Body.BodyType == BodyType.Static) continue;
|
|
|
|
msg.Write(levelWall.Body.Position.X);
|
|
msg.Write(levelWall.Body.Position.Y);
|
|
msg.WriteRangedSingle(levelWall.MoveState, 0.0f, MathHelper.TwoPi, 16);
|
|
}
|
|
}
|
|
}
|
|
}
|