(f0d812055) v0.9.9.0

This commit is contained in:
Joonas Rikkonen
2020-04-23 19:19:37 +03:00
parent b647059b93
commit ac37a3b0e4
391 changed files with 15054 additions and 5420 deletions
@@ -27,18 +27,24 @@ namespace Barotrauma
[Flags]
public enum PositionType
{
MainPath = 1, Cave = 2, Ruin = 4
MainPath = 1, Cave = 2, Ruin = 4, Wreck = 8
}
public struct InterestingPosition
{
public Point Position;
public readonly PositionType PositionType;
public bool IsValid;
public Submarine Submarine;
public Ruin Ruin;
public InterestingPosition(Point position, PositionType positionType)
public InterestingPosition(Point position, PositionType positionType, bool isValid = true, Submarine submarine = null, Ruin ruin = null)
{
Position = position;
PositionType = positionType;
IsValid = isValid;
Submarine = submarine;
Ruin = ruin;
}
}
@@ -68,6 +74,8 @@ namespace Barotrauma
private List<Ruin> ruins;
private List<Submarine> wrecks;
private LevelGenerationParams generationParams;
private List<List<Point>> smallTunnels = new List<List<Point>>();
@@ -133,11 +141,13 @@ namespace Barotrauma
get { return positionsOfInterest; }
}
public readonly List<InterestingPosition> UsedPositions = new List<InterestingPosition>();
public Submarine StartOutpost { get; private set; }
public Submarine EndOutpost { get; private set; }
private Submarine preSelectedStartOutpost;
private Submarine preSelectedEndOutpost;
private SubmarineInfo preSelectedStartOutpost;
private SubmarineInfo preSelectedEndOutpost;
public string Seed
{
@@ -210,7 +220,7 @@ namespace Barotrauma
/// </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, Submarine startOutpost = null, Submarine endOutPost = null)
public Level(string seed, float difficulty, float sizeFactor, LevelGenerationParams generationParams, Biome biome, SubmarineInfo startOutpost = null, SubmarineInfo endOutPost = null)
: base(null)
{
@@ -310,7 +320,7 @@ namespace Barotrauma
if (Submarine.MainSub != null)
{
Rectangle dockedSubBorders = Submarine.MainSub.GetDockedBorders();
dockedSubBorders.Inflate(dockedSubBorders.Size.ToVector2() * 0.05f);
dockedSubBorders.Inflate(dockedSubBorders.Size.ToVector2() * 0.15f);
minWidth = Math.Max(minWidth, Math.Max(dockedSubBorders.Width, dockedSubBorders.Height));
minWidth = Math.Min(minWidth, maxWidth);
}
@@ -669,7 +679,6 @@ namespace Barotrauma
renderer.SetWallVertices(CaveGenerator.GenerateWallShapes(cellsWithBody, this), generationParams.WallColor);
#endif
//----------------------------------------------------------------------------------
// create (placeholder) outposts at the start and end of the level
//----------------------------------------------------------------------------------
@@ -692,6 +701,12 @@ namespace Barotrauma
GenerateSeaFloor(mirror);
//----------------------------------------------------------------------------------
// create wrecks
//----------------------------------------------------------------------------------
CreateWrecks();
levelObjectManager.PlaceObjects(this, generationParams.LevelObjectAmount);
GenerateItems();
@@ -1164,7 +1179,7 @@ namespace Barotrauma
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);
", ruin size: " + ruinSize + ", selected sub " + (Submarine.MainSub == null ? "none" : Submarine.MainSub.Info.Name);
DebugConsole.ThrowError(errorMsg);
GameAnalyticsManager.AddErrorEventOnce("Level.GenerateRuins:PosNotFound", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg);
break;
@@ -1201,13 +1216,15 @@ namespace Barotrauma
ruins.Add(ruin);
ruin.RuinShapes.Sort((shape1, shape2) => shape2.DistanceFromEntrance.CompareTo(shape1.DistanceFromEntrance));
// TODO: autogenerate waypoints inside the ruins and connect them to the main path in multiple places.
// We need the waypoints for the AI navigation and we could use them for spawning the creatures too.
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));
positionsOfInterest.Add(new InterestingPosition(new Point((int)wp.WorldPosition.X, (int)wp.WorldPosition.Y), PositionType.Ruin, ruin: ruin));
waypointCount++;
}
}
@@ -1215,7 +1232,7 @@ namespace Barotrauma
//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));
positionsOfInterest.Add(new InterestingPosition(ruin.RuinShapes[i].Rect.Center, PositionType.Ruin, ruin: ruin));
}
foreach (RuinShape ruinShape in ruin.RuinShapes)
@@ -1328,8 +1345,6 @@ namespace Barotrauma
Vector2 position = Vector2.Zero;
offsetFromWall = ConvertUnits.ToSimUnits(offsetFromWall);
int tries = 0;
do
{
@@ -1398,7 +1413,7 @@ namespace Barotrauma
{
foreach (Submarine sub in Submarine.Loaded)
{
if (sub.IsOutpost) { continue; }
if (sub.Info.Type != SubmarineInfo.SubmarineType.Player) { continue; }
farEnoughPositions.RemoveAll(p => Vector2.DistanceSquared(p.Position.ToVector2(), sub.WorldPosition) < minDistFromSubs * minDistFromSubs);
}
}
@@ -1484,8 +1499,10 @@ namespace Barotrauma
return cells;
}
private readonly List<VoronoiCell> tempCells = new List<VoronoiCell>();
public List<VoronoiCell> GetCells(Vector2 worldPos, int searchDepth = 2)
{
tempCells.Clear();
int gridPosX = (int)Math.Floor(worldPos.X / GridCellSize);
int gridPosY = (int)Math.Floor(worldPos.Y / GridCellSize);
@@ -1495,12 +1512,11 @@ namespace Barotrauma
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]);
tempCells.AddRange(cellGrid[x, y]);
}
}
@@ -1508,11 +1524,317 @@ namespace Barotrauma
{
foreach (VoronoiCell cell in wall.Cells)
{
cells.Add(cell);
tempCells.Add(cell);
}
}
return cells;
return tempCells;
}
public string GetWreckIDTag(string originalTag, Submarine wreck)
{
string shortSeed = ToolBox.StringToInt(seed + wreck.Info.Name).ToString();
if (shortSeed.Length > 6) { shortSeed = shortSeed.Substring(0, 6); }
return originalTag + "_" + shortSeed;
}
// For debugging
private readonly Dictionary<Submarine, List<Vector2>> wreckPositions = new Dictionary<Submarine, List<Vector2>>();
private readonly Dictionary<Submarine, List<Rectangle>> blockedRects = new Dictionary<Submarine, List<Rectangle>>();
private void CreateWrecks()
{
var totalSW = new Stopwatch();
var tempSW = new Stopwatch();
totalSW.Start();
var wreckFiles = ContentPackage.GetFilesOfType(GameMain.Config.SelectedContentPackages, ContentType.Wreck).ToList();
if (wreckFiles.None())
{
DebugConsole.ThrowError("No wreck files found in the selected content packages!");
return;
}
wreckFiles.Shuffle(Rand.RandSync.Server);
int wreckCount = Math.Min(Loaded.GenerationParams.WreckCount, wreckFiles.Count);
// Min distance between a wreck and the start/end/other wreck.
float minDistance = Sonar.DefaultSonarRange;
float squaredMinDistance = minDistance * minDistance;
Vector2 start = startPosition.ToVector2();
Vector2 end = endPosition.ToVector2();
var waypoints = WayPoint.WayPointList.Where(wp =>
wp.Submarine == null &&
wp.SpawnType == SpawnType.Path &&
Vector2.DistanceSquared(wp.WorldPosition, start) > squaredMinDistance &&
Vector2.DistanceSquared(wp.WorldPosition, end) > squaredMinDistance).ToList();
wrecks = new List<Submarine>(wreckCount);
for (int i = 0; i < wreckCount; i++)
{
ContentFile contentFile = wreckFiles[i];
if (contentFile == null) { continue; }
var subDoc = SubmarineInfo.OpenFile(contentFile.Path);
Rectangle borders = Submarine.GetBorders(subDoc.Root);
string wreckName = System.IO.Path.GetFileNameWithoutExtension(contentFile.Path);
// Add some vertical margin so that the wreck doesn't block the path entirely. It's still possible that some larger subs can't pass by.
Point paddedDimensions = new Point(borders.Width, borders.Height + 3000);
tempSW.Restart();
// For storing the translations. Used only for debugging.
var positions = new List<Vector2>();
var rects = new List<Rectangle>();
int maxAttempts = 50;
int attemptsLeft = maxAttempts;
bool success = false;
Vector2 spawnPoint = Vector2.Zero;
while (attemptsLeft > 0)
{
if (attemptsLeft < maxAttempts)
{
Debug.WriteLine($"Failed to position the wreck {wreckName}. Trying again.");
}
attemptsLeft--;
if (TryGetSpawnPoint(out spawnPoint))
{
success = TryPositionWreck(borders, wreckName, ref spawnPoint);
if (success)
{
break;
}
else
{
positions.Clear();
}
}
else
{
DebugConsole.NewMessage($"Failed to find any spawn point for the wreck: {wreckName} (No valid waypoints left).", Color.Red);
break;
}
}
tempSW.Stop();
if (success)
{
Debug.WriteLine($"Wreck {wreckName} successfully positioned to {spawnPoint} in {tempSW.ElapsedMilliseconds.ToString()} (ms)");
tempSW.Restart();
SubmarineInfo info = new SubmarineInfo(contentFile.Path)
{
Type = SubmarineInfo.SubmarineType.Wreck
};
Submarine wreck = new Submarine(info);
wreck.MakeWreck();
tempSW.Stop();
Debug.WriteLine($"Wreck {wreck.Info.Name} loaded in { tempSW.ElapsedMilliseconds.ToString()} (ms)");
wrecks.Add(wreck);
wreck.SetPosition(spawnPoint);
wreckPositions.Add(wreck, positions);
blockedRects.Add(wreck, rects);
positionsOfInterest.Add(new InterestingPosition(spawnPoint.ToPoint(), PositionType.Wreck, submarine: wreck));
foreach (Hull hull in wreck.GetHulls(false))
{
if (Rand.Value(Rand.RandSync.Server) <= Loaded.GenerationParams.WreckHullFloodingChance)
{
hull.WaterVolume = hull.Volume * Rand.Range(Loaded.GenerationParams.WreckFloodingHullMinWaterPercentage, Loaded.GenerationParams.WreckFloodingHullMaxWaterPercentage, Rand.RandSync.Server);
}
}
if (Rand.Value(Rand.RandSync.Server) <= Loaded.GenerationParams.ThalamusProbability)
{
if (!wreck.CreateWreckAI())
{
DebugConsole.NewMessage($"Failed to create wreck AI inside {wreckName}.", Color.Red);
wreck.DisableWreckAI();
}
}
else
{
wreck.DisableWreckAI();
}
}
else
{
DebugConsole.NewMessage($"Failed to position wreck {wreckName}. Used {tempSW.ElapsedMilliseconds.ToString()} (ms).", Color.Red);
}
bool TryPositionWreck(Rectangle borders, string wreckName, ref Vector2 spawnPoint)
{
positions.Add(spawnPoint);
bool bottomFound = TryRaycastToBottom(borders, ref spawnPoint);
positions.Add(spawnPoint);
bool leftSideBlocked = IsSideBlocked(borders, false);
bool rightSideBlocked = IsSideBlocked(borders, true);
int step = 5;
if (rightSideBlocked && !leftSideBlocked)
{
bottomFound = TryMove(borders, ref spawnPoint, -step);
}
else if (leftSideBlocked && !rightSideBlocked)
{
bottomFound = TryMove(borders, ref spawnPoint, step);
}
else if (!bottomFound)
{
if (!leftSideBlocked)
{
bottomFound = TryMove(borders, ref spawnPoint, -step);
}
else if (!rightSideBlocked)
{
bottomFound = TryMove(borders, ref spawnPoint, step);
}
else
{
Debug.WriteLine($"Invalid position {spawnPoint}. Does not touch the ground.");
return false;
}
}
positions.Add(spawnPoint);
bool isBlocked = IsBlocked(spawnPoint, borders.Size - new Point(step + 50));
if (isBlocked)
{
rects.Add(ToolBox.GetWorldBounds(spawnPoint.ToPoint(), borders.Size));
Debug.WriteLine($"Invalid position {spawnPoint}. Blocked by level walls.");
}
else if (!bottomFound)
{
Debug.WriteLine($"Invalid position {spawnPoint}. Does not touch the ground.");
}
else
{
var sp = spawnPoint;
if (wrecks.Any(w => Vector2.DistanceSquared(w.WorldPosition, sp) < squaredMinDistance))
{
Debug.WriteLine($"Invalid position {spawnPoint}. Too close to other wreck(s).");
return false;
}
}
return !isBlocked && bottomFound;
bool TryMove(Rectangle borders, ref Vector2 spawnPoint, float amount)
{
float maxMovement = 5000;
float totalAmount = 0;
bool foundBottom = TryRaycastToBottom(borders, ref spawnPoint);
while (!IsSideBlocked(borders, amount > 0))
{
foundBottom = TryRaycastToBottom(borders, ref spawnPoint);
totalAmount += amount;
spawnPoint = new Vector2(spawnPoint.X + amount, spawnPoint.Y);
if (Math.Abs(totalAmount) > maxMovement)
{
Debug.WriteLine($"Moving the wreck {wreckName} failed.");
break;
}
}
return foundBottom;
}
}
bool TryGetSpawnPoint(out Vector2 spawnPoint)
{
spawnPoint = Vector2.Zero;
while (waypoints.Any())
{
var wp = waypoints.GetRandom(Rand.RandSync.Server);
waypoints.Remove(wp);
if (!IsBlocked(wp.WorldPosition, paddedDimensions))
{
spawnPoint = wp.WorldPosition;
return true;
}
}
return false;
}
static bool TryRaycastToBottom(Rectangle borders, ref Vector2 spawnPoint)
{
// Shoot five rays and pick the highest hit point.
int rayCount = 5;
var positions = new Vector2[rayCount];
bool hit = false;
for (int i = 0; i < rayCount; i++)
{
float quarterWidth = borders.Width * 0.25f;
Vector2 rayStart = spawnPoint;
switch (i)
{
case 1:
rayStart = new Vector2(spawnPoint.X - quarterWidth, spawnPoint.Y);
break;
case 2:
rayStart = new Vector2(spawnPoint.X + quarterWidth, spawnPoint.Y);
break;
case 3:
rayStart = new Vector2(spawnPoint.X - quarterWidth / 2, spawnPoint.Y);
break;
case 4:
rayStart = new Vector2(spawnPoint.X + quarterWidth / 2, spawnPoint.Y);
break;
}
var simPos = ConvertUnits.ToSimUnits(rayStart);
var body = Submarine.PickBody(simPos, new Vector2(simPos.X, -1),
customPredicate: f => f.Body?.UserData is VoronoiCell cell && cell.Body.BodyType == BodyType.Static,
collisionCategory: Physics.CollisionLevel | Physics.CollisionWall);
if (body != null)
{
positions[i] = ConvertUnits.ToDisplayUnits(Submarine.LastPickedPosition) + new Vector2(0, borders.Height / 2);
hit = true;
}
}
float highestPoint = positions.Max(p => p.Y);
spawnPoint = new Vector2(spawnPoint.X, highestPoint);
return hit;
}
bool IsSideBlocked(Rectangle borders, bool front)
{
// Shoot three rays and check whether any of them hits.
int rayCount = 3;
Vector2 halfSize = borders.Size.ToVector2() / 2;
Vector2 quarterSize = halfSize / 2;
var positions = new Vector2[rayCount];
for (int i = 0; i < rayCount; i++)
{
float dir = front ? 1 : -1;
Vector2 rayStart;
Vector2 to;
switch (i)
{
case 1:
rayStart = new Vector2(spawnPoint.X + halfSize.X * dir, spawnPoint.Y + quarterSize.Y);
to = new Vector2(spawnPoint.X + (halfSize.X - quarterSize.X) * dir, rayStart.Y);
break;
case 2:
rayStart = new Vector2(spawnPoint.X + halfSize.X * dir, spawnPoint.Y - quarterSize.Y);
to = new Vector2(spawnPoint.X + (halfSize.X - quarterSize.X) * dir, rayStart.Y);
break;
case 0:
default:
rayStart = spawnPoint;
to = new Vector2(spawnPoint.X + halfSize.X * dir, rayStart.Y);
break;
}
Vector2 simPos = ConvertUnits.ToSimUnits(rayStart);
if (Submarine.PickBody(simPos, ConvertUnits.ToSimUnits(to),
customPredicate: f => f.Body?.UserData is VoronoiCell cell,
collisionCategory: Physics.CollisionLevel | Physics.CollisionWall) != null)
{
return true;
}
}
return false;
}
bool IsBlocked(Vector2 pos, Point size, float maxDistanceMultiplier = 1)
{
float maxDistance = size.Multiply(maxDistanceMultiplier).ToVector2().LengthSquared();
Rectangle bounds = ToolBox.GetWorldBounds(pos.ToPoint(), size);
if (ruins.Any(r => ToolBox.GetWorldBounds(r.Area.Center, r.Area.Size).IntersectsWorld(bounds)))
{
return true;
}
var cells = Loaded.GetAllCells().Where(c => c.Body != null && Vector2.DistanceSquared(pos, c.Center) <= maxDistance);
return cells.Any(c => c.BodyVertices.Any(v => bounds.ContainsWorld(v)));
}
}
totalSW.Stop();
Debug.WriteLine($"{wrecks.Count} wrecks created in { totalSW.ElapsedMilliseconds.ToString()} (ms)");
}
private void CreateOutposts()
@@ -1537,20 +1859,20 @@ namespace Barotrauma
continue;
}
Submarine outpost = null;
SubmarineInfo outpostInfo = null;
if (i == 0 && preSelectedStartOutpost == null || i == 1 && preSelectedEndOutpost == null)
{
string outpostFile = outpostFiles.GetRandom(Rand.RandSync.Server).Path;
outpost = new Submarine(outpostFile, tryLoad: false);
outpostInfo = new SubmarineInfo(outpostFile);
}
else
{
outpost = (i == 0) ? preSelectedStartOutpost : preSelectedEndOutpost;
outpostInfo = (i == 0) ? preSelectedStartOutpost : preSelectedEndOutpost;
}
outpost.Load(unloadPrevious: false);
outpost.MakeOutpost();
outpostInfo.Type = SubmarineInfo.SubmarineType.Outpost;
var outpost = new Submarine(outpostInfo);
Point? minSize = null;
DockingPort subPort = null;
@@ -1597,9 +1919,9 @@ namespace Barotrauma
if (Math.Abs(subDockingPortOffset) > 5000.0f)
{
subDockingPortOffset = MathHelper.Clamp(subDockingPortOffset, -5000.0f, 5000.0f);
string warningMsg = "Docking port very far from the sub's center of mass (submarine: " + Submarine.MainSub.Name + ", dist: " + subDockingPortOffset + "). The level generator may not be able to place the outpost so that docking is possible.";
string warningMsg = "Docking port very far from the sub's center of mass (submarine: " + Submarine.MainSub.Info.Name + ", dist: " + subDockingPortOffset + "). The level generator may not be able to place the outpost so that docking is possible.";
DebugConsole.NewMessage(warningMsg, Color.Orange);
GameAnalyticsManager.AddErrorEventOnce("Lever.CreateOutposts:DockingPortVeryFar" + Submarine.MainSub.Name, GameAnalyticsSDK.Net.EGAErrorSeverity.Warning, warningMsg);
GameAnalyticsManager.AddErrorEventOnce("Lever.CreateOutposts:DockingPortVeryFar" + Submarine.MainSub.Info.Name, GameAnalyticsSDK.Net.EGAErrorSeverity.Warning, warningMsg);
}
float outpostDockingPortOffset = subPort == null ? 0.0f : outpostPort.Item.WorldPosition.X - outpost.WorldPosition.X;
@@ -1607,21 +1929,21 @@ namespace Barotrauma
if (Math.Abs(outpostDockingPortOffset) > 5000.0f)
{
outpostDockingPortOffset = MathHelper.Clamp(outpostDockingPortOffset, -5000.0f, 5000.0f);
string warningMsg = "Docking port very far from the outpost's center of mass (outpost: " + outpost.Name + ", dist: " + outpostDockingPortOffset + "). The level generator may not be able to place the outpost so that docking is possible.";
string warningMsg = "Docking port very far from the outpost's center of mass (outpost: " + outpost.Info.Name + ", dist: " + outpostDockingPortOffset + "). The level generator may not be able to place the outpost so that docking is possible.";
DebugConsole.NewMessage(warningMsg, Color.Orange);
GameAnalyticsManager.AddErrorEventOnce("Lever.CreateOutposts:OutpostDockingPortVeryFar" + outpost.Name, GameAnalyticsSDK.Net.EGAErrorSeverity.Warning, warningMsg);
GameAnalyticsManager.AddErrorEventOnce("Lever.CreateOutposts:OutpostDockingPortVeryFar" + outpost.Info.Name, GameAnalyticsSDK.Net.EGAErrorSeverity.Warning, warningMsg);
}
outpost.SetPosition(outpost.FindSpawnPos(i == 0 ? StartPosition : EndPosition, minSize, subDockingPortOffset - outpostDockingPortOffset));
if ((i == 0) == !Mirrored)
{
StartOutpost = outpost;
if (GameMain.GameSession?.StartLocation != null) { outpost.Name = GameMain.GameSession.StartLocation.Name; }
if (GameMain.GameSession?.StartLocation != null) { outpost.Info.Name = GameMain.GameSession.StartLocation.Name; }
}
else
{
EndOutpost = outpost;
if (GameMain.GameSession?.EndLocation != null) { outpost.Name = GameMain.GameSession.EndLocation.Name; }
if (GameMain.GameSession?.EndLocation != null) { outpost.Info.Name = GameMain.GameSession.EndLocation.Name; }
}
}
}
@@ -1635,6 +1957,93 @@ namespace Barotrauma
#endif
}
public void SpawnCorpses()
{
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) { return; }
foreach (Submarine wreck in wrecks)
{
int corpseCount = Rand.Range(Loaded.GenerationParams.MinCorpseCount, Loaded.GenerationParams.MaxCorpseCount);
var allSpawnPoints = WayPoint.WayPointList.FindAll(wp => wp.Submarine == wreck && wp.CurrentHull != null);
var pathPoints = allSpawnPoints.FindAll(wp => wp.SpawnType == SpawnType.Path);
pathPoints.Shuffle(Rand.RandSync.Unsynced);
var corpsePoints = allSpawnPoints.FindAll(wp => wp.SpawnType == SpawnType.Corpse);
corpsePoints.Shuffle(Rand.RandSync.Unsynced);
int spawnCounter = 0;
for (int j = 0; j < corpseCount; j++)
{
WayPoint sp = corpsePoints.FirstOrDefault() ?? pathPoints.FirstOrDefault();
JobPrefab job = sp?.AssignedJob;
CorpsePrefab selectedPrefab;
if (job == null)
{
// Deduce the job from the selected prefab
selectedPrefab = GetCorpsePrefab(p => p.SpawnPosition == PositionType.Wreck);
job = GetJobPrefab();
}
else
{
selectedPrefab = GetCorpsePrefab(p => p.SpawnPosition == PositionType.Wreck && (p.Job == "any" || p.Job == job.Identifier));
if (selectedPrefab == null)
{
corpsePoints.Remove(sp);
pathPoints.Remove(sp);
sp = corpsePoints.FirstOrDefault(sp => sp.AssignedJob == null) ?? pathPoints.FirstOrDefault(sp => sp.AssignedJob == null);
// Deduce the job from the selected prefab
selectedPrefab = GetCorpsePrefab(p => p.SpawnPosition == PositionType.Wreck);
job = GetJobPrefab();
}
}
if (selectedPrefab == null) { continue; }
Vector2 worldPos;
if (sp == null)
{
if (!TryGetExtraSpawnPoint(out worldPos))
{
break;
}
job = GetJobPrefab();
}
else
{
worldPos = sp.WorldPosition;
corpsePoints.Remove(sp);
pathPoints.Remove(sp);
}
if (job == null) { continue; }
var characterInfo = new CharacterInfo(CharacterPrefab.HumanSpeciesName, jobPrefab: job);
var corpse = Character.Create(CharacterPrefab.HumanConfigFile, worldPos, ToolBox.RandomSeed(8), characterInfo, hasAi: true, createNetworkEvent: true);
corpse.AnimController.FindHull(worldPos, true);
corpse.TeamID = Character.TeamType.None;
corpse.EnableDespawn = false;
selectedPrefab.GiveItems(corpse, wreck);
corpse.Kill(CauseOfDeathType.Unknown, causeOfDeathAffliction: null, log: false);
spawnCounter++;
static CorpsePrefab GetCorpsePrefab(Func<CorpsePrefab, bool> predicate)
{
IEnumerable<CorpsePrefab> filteredPrefabs = CorpsePrefab.Prefabs.Where(predicate);
return ToolBox.SelectWeightedRandom(filteredPrefabs.ToList(), filteredPrefabs.Select(p => p.Commonness).ToList(), Rand.RandSync.Unsynced);
}
JobPrefab GetJobPrefab() => selectedPrefab.Job != null && selectedPrefab.Job != "any" ? JobPrefab.Get(selectedPrefab.Job) : JobPrefab.Random();
}
#if DEBUG
DebugConsole.NewMessage($"{spawnCounter}/{corpseCount} corpses spawned in {wreck.Info.Name}.", spawnCounter == corpseCount ? Color.Green : Color.Yellow);
#endif
bool TryGetExtraSpawnPoint(out Vector2 point)
{
point = Vector2.Zero;
var hull = Hull.hullList.FindAll(h => h.Submarine == wreck).GetRandom();
if (hull != null)
{
point = hull.WorldPosition;
}
return hull != null;
}
}
}
public override void Remove()
{
base.Remove();