632 lines
27 KiBLFS
C#
Executable File
632 lines
27 KiBLFS
C#
Executable File
|
|
using Barotrauma;
|
|
using Barotrauma.Extensions;
|
|
using Barotrauma.Items.Components;
|
|
using Barotrauma.MoreLevelContent.Config;
|
|
using FarseerPhysics;
|
|
using Microsoft.Xna.Framework;
|
|
using MoreLevelContent.Missions;
|
|
using MoreLevelContent.Shared.Data;
|
|
using MoreLevelContent.Shared.Generation.Interfaces;
|
|
using MoreLevelContent.Shared.Store;
|
|
using MoreLevelContent.Shared.Utils;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Diagnostics;
|
|
using System.Linq;
|
|
using Voronoi2;
|
|
using static Barotrauma.Level;
|
|
|
|
namespace MoreLevelContent.Shared.Generation
|
|
{
|
|
public class MissionGenerationDirector : GenerationDirector<MissionGenerationDirector>, IGenerateSubmarine, IGenerateNPCs
|
|
{
|
|
public override bool Active => true;
|
|
|
|
readonly Queue<SubmarineSpawnRequest> SubCreationQueue = new();
|
|
readonly Queue<DecoSpawnRequest> DecoCreationQueue = new();
|
|
readonly Queue<(Submarine Sub, float SkipChance)> AutoFillQueue = new();
|
|
internal delegate void OnSubmarineCreated(Submarine createdSubmarine);
|
|
internal delegate void OnDecoCreated(List<Submarine> decoItems, Cave decoratedCave);
|
|
public static List<(Vector2, Vector2)> DebugPoints = new();
|
|
|
|
internal static void RequestSubmarine(SubmarineSpawnRequest info) =>
|
|
Instance.SubCreationQueue.Enqueue(info);
|
|
internal static void RequestStaticSubmarine(ContentFile contentFile, OnSubmarineCreated onSubmarineCreated, bool autoFill = true) =>
|
|
Instance.RequestStaticSub(contentFile, onSubmarineCreated, autoFill);
|
|
internal static void RequestSubmarine(ContentFile contentFile, OnSubmarineCreated onSubmarineCreated, bool autoFill = true) =>
|
|
Instance.RequestSub(contentFile, onSubmarineCreated, autoFill);
|
|
internal static void RequestDecorate(List<ContentFile> files, OnDecoCreated onDecoCreated, bool autoFill = false) => Instance.RequestDeco(files, onDecoCreated, autoFill);
|
|
|
|
struct DecoSpawnRequest
|
|
{
|
|
public List<ContentFile> ContentFiles;
|
|
public OnDecoCreated Callback;
|
|
public bool AutoFill;
|
|
|
|
public DecoSpawnRequest(List<ContentFile> contentFiles, OnDecoCreated callback, bool autoFill)
|
|
{
|
|
ContentFiles = contentFiles;
|
|
Callback = callback;
|
|
AutoFill = autoFill;
|
|
}
|
|
}
|
|
|
|
internal struct SubmarineSpawnRequest
|
|
{
|
|
public ContentFile File;
|
|
public OnSubmarineCreated Callback;
|
|
public bool AutoFill = false;
|
|
public bool AllowStealing = true;
|
|
public AutoFillPrefix Prefix = AutoFillPrefix.None;
|
|
public SubSpawnPosition SpawnPosition = SubSpawnPosition.PathWall;
|
|
public PlacementType PlacementType = PlacementType.Bottom;
|
|
public bool IgnoreCrushDpeth = true;
|
|
public float SkipItemChance = 0.5f;
|
|
|
|
public SubmarineSpawnRequest()
|
|
{
|
|
File = null;
|
|
Callback = null;
|
|
}
|
|
|
|
public enum AutoFillPrefix
|
|
{
|
|
None,
|
|
Wreck,
|
|
Abandoned
|
|
}
|
|
}
|
|
|
|
void RequestStaticSub(ContentFile contentFile, OnSubmarineCreated onSubmarineCreated, bool autoFill)
|
|
{
|
|
SubCreationQueue.Enqueue(new SubmarineSpawnRequest()
|
|
{
|
|
File = contentFile,
|
|
Callback = onSubmarineCreated,
|
|
AutoFill = autoFill
|
|
});
|
|
Log.Debug("Enqueued spawn request for submarine");
|
|
}
|
|
|
|
void RequestSub(ContentFile contentFile, OnSubmarineCreated onSubmarineCreated, bool autoFill)
|
|
{
|
|
SubCreationQueue.Enqueue(new SubmarineSpawnRequest() {
|
|
File = contentFile,
|
|
Callback = onSubmarineCreated,
|
|
AutoFill = autoFill,
|
|
SpawnPosition = SubSpawnPosition.PathWall
|
|
});
|
|
Log.Debug("Enqueued spawn request for submarine on path");
|
|
}
|
|
|
|
void RequestDeco(List<ContentFile> files, OnDecoCreated onDecoCreated, bool autoFill)
|
|
{
|
|
DecoCreationQueue.Enqueue(new DecoSpawnRequest(files, onDecoCreated, autoFill));
|
|
Log.Debug($"Enqueued spawn request for cave decoration with {files.Count} items");
|
|
}
|
|
|
|
public void GenerateSub()
|
|
{
|
|
SpawnConstructionSite();
|
|
SpawnRelayStation();
|
|
SpawnRequestedSubs();
|
|
DecorateCaves();
|
|
}
|
|
|
|
void SpawnRequestedSubs()
|
|
{
|
|
DebugPoints.Clear();
|
|
while (SubCreationQueue.Count > 0)
|
|
{
|
|
SubmarineSpawnRequest request = SubCreationQueue.Dequeue();
|
|
string subName = System.IO.Path.GetFileNameWithoutExtension(request.File.Path.Value);
|
|
Submarine submarine;
|
|
if (request.SpawnPosition == SubSpawnPosition.PathWall)
|
|
{
|
|
submarine = SpawnSubOnPath(subName, request.File, ignoreCrushDepth: request.IgnoreCrushDpeth, placementType: request.PlacementType);
|
|
if (submarine == null)
|
|
{
|
|
Log.Error("Failed to spawn submarine at requested location, spawning it anywhere...");
|
|
submarine = SpawnSub(request.File);
|
|
if (Level.Loaded.TryGetInterestingPosition(true, Level.PositionType.MainPath, Level.Loaded.Size.X * 0.3f, out var potentialSpawnPos))
|
|
{
|
|
submarine.SetPosition(submarine.FindSpawnPos(potentialSpawnPos.Position.ToVector2()));
|
|
} else
|
|
{
|
|
submarine.SetPosition(submarine.FindSpawnPos(Level.Loaded.EndPosition));
|
|
}
|
|
}
|
|
} else
|
|
{
|
|
if (request.SpawnPosition == SubSpawnPosition.AbyssIsland)
|
|
{
|
|
submarine = PositionAbyssCave(request);
|
|
} else
|
|
{
|
|
submarine = SpawnSub(request.File);
|
|
}
|
|
}
|
|
|
|
if (submarine != null)
|
|
{
|
|
Log.Debug($"Spawned requested submarine {subName}");
|
|
request.Callback.Invoke(submarine);
|
|
|
|
if (request.AutoFill)
|
|
{
|
|
foreach (Item item in Item.ItemList)
|
|
{
|
|
if (item.Submarine != submarine) { continue; }
|
|
if (item.NonInteractable) { continue; }
|
|
item.AllowStealing = request.AllowStealing;
|
|
if (item.GetRootInventoryOwner() is Character) { continue; }
|
|
IEnumerable<Identifier> splitTags = item.Tags.Split(',').ToIdentifiers();
|
|
int len = splitTags.Count();
|
|
foreach (var tag in splitTags)
|
|
{
|
|
if (request.Prefix != SubmarineSpawnRequest.AutoFillPrefix.None)
|
|
{
|
|
item.AddTag($"{request.Prefix}{tag}");
|
|
}
|
|
}
|
|
foreach (var container in item.GetComponents<ItemContainer>())
|
|
{
|
|
container.AutoFill = true;
|
|
}
|
|
}
|
|
Log.Debug("Filed auto fill request for submarine");
|
|
AutoFillQueue.Enqueue((submarine, request.SkipItemChance));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Log.Error($"Failed to spawn requested submarine!");
|
|
}
|
|
}
|
|
}
|
|
|
|
void SpawnConstructionSite()
|
|
{
|
|
if (Level.Loaded.LevelData.MLC().HasBeaconConstruction && ConfigManager.Instance.Config.NetworkedConfig.GeneralConfig.EnableConstructionSites)
|
|
{
|
|
Submarine beacon = SpawnSubOnPath("Construction Site", BeaconConstStore.Instance.GetBeaconForLevel(), ignoreCrushDepth: true, SubmarineType.EnemySubmarine);
|
|
beacon.PhysicsBody.BodyType = BodyType.Static;
|
|
beacon.Info.Type = SubmarineType.BeaconStation;
|
|
beacon.ShowSonarMarker = false;
|
|
Level.Loaded.MLC().BeaconConstructionStation = beacon;
|
|
Item storageItem = Item.ItemList.Find(it => it.Submarine == beacon && it.GetComponent<ItemContainer>() != null && it.Tags.Contains("dropoff"));
|
|
if (storageItem == null)
|
|
{
|
|
Log.Error($"Unable to find the drop off point for beacon construction {beacon.Info.Name}!");
|
|
return;
|
|
}
|
|
Level.Loaded.MLC().DropOffPoint = storageItem;
|
|
|
|
}
|
|
}
|
|
|
|
void SpawnRelayStation()
|
|
{
|
|
if (Loaded.LevelData.MLC().RelayStationStatus == RelayStationStatus.None) return;
|
|
Log.Debug($"Trying to spawn relay station for status {Loaded.LevelData.MLC().RelayStationStatus}");
|
|
if (CablePuzzleMission.SubmarineFile == null)
|
|
{
|
|
Log.Debug("Sub file was null, how did this happen? Skipping attempting to make the relay station.");
|
|
return;
|
|
}
|
|
Submarine relayStation = SpawnSubOnPath("Relay Station", CablePuzzleMission.SubmarineFile, ignoreCrushDepth: true, SubmarineType.EnemySubmarine, PlacementType.Top);
|
|
if (relayStation == null)
|
|
{
|
|
Log.Error("Failed to spawn relay station");
|
|
return;
|
|
}
|
|
Log.Debug("Spawned relay station");
|
|
relayStation.PhysicsBody.FarseerBody.BodyType = FarseerPhysics.BodyType.Static;
|
|
relayStation.TeamID = CharacterTeamType.FriendlyNPC;
|
|
relayStation.ShowSonarMarker = false;
|
|
Loaded.MLC().RelayStation = relayStation;
|
|
}
|
|
|
|
void DecorateCaves()
|
|
{
|
|
Log.Debug("Decorating Caves");
|
|
while (DecoCreationQueue.Count > 0)
|
|
{
|
|
var request = DecoCreationQueue.Dequeue();
|
|
Cave cave = Loaded.Caves.GetRandom(Rand.RandSync.ServerAndClient);
|
|
List<Submarine> deco = new();
|
|
|
|
foreach (var file in request.ContentFiles)
|
|
{
|
|
try
|
|
{
|
|
Log.Debug($"Trying to spawn deco {file.Path}");
|
|
var tunnel = cave.Tunnels.GetRandom(Rand.RandSync.ServerAndClient);
|
|
var pos = tunnel.WayPoints.GetRandom(Rand.RandSync.ServerAndClient);
|
|
Submarine sub = SpawnSubAtPosition("Cave Deco", file, pos.Position);
|
|
if (sub != null) deco.Add(sub);
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
Log.Error(e.ToString());
|
|
}
|
|
}
|
|
|
|
request.Callback?.Invoke(deco, cave);
|
|
}
|
|
}
|
|
|
|
public override void Setup() => BeaconConstStore.Instance.Setup();
|
|
public void SpawnNPCs()
|
|
{
|
|
Log.Debug("Auto filling subs");
|
|
if (AutoFillQueue.Count == 0) Log.Debug("No subs in autofill queue");
|
|
while (AutoFillQueue.Count > 0)
|
|
{
|
|
try
|
|
{
|
|
(Submarine, float)request = AutoFillQueue.Dequeue();
|
|
AutofillSub(request.Item1, request.Item2);
|
|
Log.Debug("Auto filled submarine");
|
|
} catch(Exception e)
|
|
{
|
|
Log.Debug(e.ToString());
|
|
}
|
|
}
|
|
}
|
|
|
|
void PositionAbyss(Submarine sub)
|
|
{
|
|
Log.Error("Position Type Abyss is not implemented");
|
|
}
|
|
|
|
Submarine PositionAbyssCave(SubmarineSpawnRequest request)
|
|
{
|
|
var subDoc = SubmarineInfo.OpenFile(request.File.Path.Value);
|
|
Rectangle subBorders = Submarine.GetBorders(subDoc.Root);
|
|
SubmarineInfo info = new SubmarineInfo(request.File.Path.Value);
|
|
|
|
int maxAttempts = 25;
|
|
int attemptsLeft = maxAttempts;
|
|
var rand = MLCUtils.GetLevelRandom();
|
|
var validIslands = Loaded.AbyssIslands.Where(i => !Loaded.Caves.Any(c => c.Area.Intersects(i.Area))).ToList();
|
|
if (!validIslands.Any())
|
|
{
|
|
// If we found NO islands, tolerate spawning on caves
|
|
validIslands = Loaded.AbyssIslands;
|
|
}
|
|
Vector2 startPoint = default;
|
|
bool foundPos = false;
|
|
int offset = 1;
|
|
int dir = request.PlacementType == PlacementType.Bottom ? 1 : -1;
|
|
|
|
|
|
SpawnOnIsland(validIslands);
|
|
|
|
if (!foundPos)
|
|
{
|
|
Log.Error("Failed to find a spawn position");
|
|
return null;
|
|
}
|
|
|
|
Submarine sub = new Submarine(info);
|
|
sub.SetPosition(startPoint, forceUndockFromStaticSubmarines: false);
|
|
return sub;
|
|
|
|
void SpawnOnIsland(List<AbyssIsland> islands)
|
|
{
|
|
var island = validIslands.GetRandom(rand);
|
|
if (island == null)
|
|
{
|
|
Log.Debug("Failed to find a valid island to spawn on");
|
|
return;
|
|
}
|
|
startPoint = island.Area.Center.ToVector2();
|
|
|
|
// Check if position is overlapping
|
|
while (attemptsLeft > 0)
|
|
{
|
|
if (TryPosition())
|
|
{
|
|
foundPos = true;
|
|
return;
|
|
}
|
|
offset++;
|
|
}
|
|
|
|
// We found no position for this island
|
|
// Remove it and try again if we still have islands left
|
|
_ = validIslands.Remove(island);
|
|
attemptsLeft = maxAttempts;
|
|
if (islands.Count == 0)
|
|
{
|
|
Log.Error("NO valid abyss islands found :((");
|
|
return;
|
|
}
|
|
SpawnOnIsland(islands);
|
|
|
|
bool TryPosition()
|
|
{
|
|
float halfHeight = subBorders.Height / 10;
|
|
float startY = startPoint.Y + (halfHeight * offset * dir);
|
|
float x1 = startPoint.X - (subBorders.Width / 2);
|
|
float x2 = startPoint.X;
|
|
float x3 = startPoint.X + (subBorders.Width / 2);
|
|
|
|
|
|
Vector2 rayStart = new Vector2(x2, startY);
|
|
Vector2 to = new Vector2(x2, startY - (halfHeight * dir));
|
|
DebugPoints.Add((rayStart, to));
|
|
|
|
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,
|
|
allowInsideFixture: true) != null)
|
|
{
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
startPoint = new Vector2(to.X, to.Y + ((subBorders.Height / 2) * dir));
|
|
//for (int i = 0; i < 50; i++)
|
|
//{
|
|
// if (Slam()) break;
|
|
//}
|
|
return true;
|
|
}
|
|
|
|
bool Slam()
|
|
{
|
|
if (Submarine.PickBody(simPos, ConvertUnits.ToSimUnits(to),
|
|
customPredicate: f => f.Body?.UserData is VoronoiCell cell,
|
|
collisionCategory: Physics.CollisionLevel | Physics.CollisionWall,
|
|
allowInsideFixture: true) != null)
|
|
{
|
|
return false;
|
|
} else
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
private Submarine SpawnSub(ContentFile contentFile)
|
|
{
|
|
SubmarineInfo info = new SubmarineInfo(contentFile.Path.Value);
|
|
Submarine sub = new Submarine(info);
|
|
return sub;
|
|
}
|
|
|
|
private Submarine SpawnSubAtPosition(string subName, ContentFile contentFile, Vector2 spawnPoint)
|
|
{
|
|
var tempSW = new Stopwatch();
|
|
|
|
// Min distance between a sub and the start/end/other sub.
|
|
var subDoc = SubmarineInfo.OpenFile(contentFile.Path.Value);
|
|
Rectangle subBorders = Submarine.GetBorders(subDoc.Root);
|
|
Point paddedDimensions = new Point(subBorders.Width, subBorders.Height);
|
|
|
|
var positions = new List<Vector2>();
|
|
var rects = new List<Rectangle>();
|
|
int maxAttempts = 50;
|
|
int attemptsLeft = maxAttempts;
|
|
bool success = true;
|
|
var allCells = Loaded.GetAllCells();
|
|
while (attemptsLeft > 0)
|
|
{
|
|
if (attemptsLeft < maxAttempts)
|
|
{
|
|
Log.Debug($"Failed to position the sub {subName}. Trying again.");
|
|
}
|
|
attemptsLeft--;
|
|
success = TryPositionSub(subBorders, subName, ref spawnPoint);
|
|
if (success)
|
|
{
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
positions.Clear();
|
|
}
|
|
}
|
|
|
|
tempSW.Stop();
|
|
if (success)
|
|
{
|
|
Log.Debug($"Sub {subName} successfully positioned to {spawnPoint} in {tempSW.ElapsedMilliseconds} (ms)");
|
|
tempSW.Restart();
|
|
try
|
|
{
|
|
SubmarineInfo info = new SubmarineInfo(contentFile.Path.Value);
|
|
Submarine sub = new Submarine(info);
|
|
tempSW.Stop();
|
|
Log.Debug($"Sub {sub.Info.Name} loaded in {tempSW.ElapsedMilliseconds} (ms)");
|
|
sub.PhysicsBody.BodyType = BodyType.Static;
|
|
sub.SetPosition(spawnPoint);
|
|
return sub;
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
Log.Error(e.ToString());
|
|
return null;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Log.Error($"Failed to position wreck {subName}. Used {tempSW.ElapsedMilliseconds} (ms).");
|
|
return null;
|
|
}
|
|
|
|
bool TryPositionSub(Rectangle subBorders, string subName, ref Vector2 spawnPoint)
|
|
{
|
|
positions.Add(spawnPoint);
|
|
bool bottomFound = TryRaycastToBottom(subBorders, ref spawnPoint);
|
|
positions.Add(spawnPoint);
|
|
|
|
bool leftSideBlocked = IsSideBlocked(subBorders, false);
|
|
bool rightSideBlocked = IsSideBlocked(subBorders, true);
|
|
int step = 5;
|
|
if (rightSideBlocked && !leftSideBlocked)
|
|
{
|
|
bottomFound = TryMove(subBorders, ref spawnPoint, -step);
|
|
}
|
|
else if (leftSideBlocked && !rightSideBlocked)
|
|
{
|
|
bottomFound = TryMove(subBorders, ref spawnPoint, step);
|
|
}
|
|
else if (!bottomFound)
|
|
{
|
|
if (!leftSideBlocked)
|
|
{
|
|
bottomFound = TryMove(subBorders, ref spawnPoint, -step);
|
|
}
|
|
else if (!rightSideBlocked)
|
|
{
|
|
bottomFound = TryMove(subBorders, ref spawnPoint, step);
|
|
}
|
|
else
|
|
{
|
|
Log.Debug($"Invalid position {spawnPoint}. Does not touch the ground.");
|
|
return false;
|
|
}
|
|
}
|
|
positions.Add(spawnPoint);
|
|
bool isBlocked = IsBlocked(spawnPoint, subBorders.Size - new Point(step + 50));
|
|
if (isBlocked)
|
|
{
|
|
rects.Add(ToolBox.GetWorldBounds(spawnPoint.ToPoint(), subBorders.Size));
|
|
Log.Debug($"Invalid position {spawnPoint}. Blocked by level walls.");
|
|
}
|
|
else if (!bottomFound)
|
|
{
|
|
Log.Debug($"Invalid position {spawnPoint}. Does not touch the ground.");
|
|
}
|
|
else
|
|
{
|
|
var sp = spawnPoint;
|
|
}
|
|
return !isBlocked && bottomFound;
|
|
|
|
bool TryMove(Rectangle subBorders, ref Vector2 spawnPoint, float amount)
|
|
{
|
|
float maxMovement = 5000;
|
|
float totalAmount = 0;
|
|
bool foundBottom = TryRaycastToBottom(subBorders, ref spawnPoint);
|
|
while (!IsSideBlocked(subBorders, amount > 0))
|
|
{
|
|
foundBottom = TryRaycastToBottom(subBorders, ref spawnPoint);
|
|
totalAmount += amount;
|
|
spawnPoint = new Vector2(spawnPoint.X + amount, spawnPoint.Y);
|
|
if (Math.Abs(totalAmount) > maxMovement)
|
|
{
|
|
Debug.WriteLine($"Moving the sub {subName} failed.");
|
|
break;
|
|
}
|
|
}
|
|
return foundBottom;
|
|
}
|
|
}
|
|
|
|
bool TryRaycastToBottom(Rectangle subBorders, 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 = subBorders.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 && !Loaded.ExtraWalls.Any(w => w.Body == f.Body),
|
|
collisionCategory: Physics.CollisionLevel | Physics.CollisionWall);
|
|
if (body != null)
|
|
{
|
|
positions[i] = ConvertUnits.ToDisplayUnits(Submarine.LastPickedPosition) + new Vector2(0, subBorders.Height / 2);
|
|
hit = true;
|
|
}
|
|
}
|
|
float highestPoint = positions.Max(p => p.Y);
|
|
spawnPoint = new Vector2(spawnPoint.X, highestPoint);
|
|
return hit;
|
|
}
|
|
|
|
bool IsSideBlocked(Rectangle subBorders, bool front)
|
|
{
|
|
// Shoot three rays and check whether any of them hits.
|
|
int rayCount = 3;
|
|
Vector2 halfSize = subBorders.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);
|
|
return Loaded.GetAllCells().Any(c => c.Body != null && Vector2.DistanceSquared(pos, c.Center) <= maxDistance && c.BodyVertices.Any(v => bounds.ContainsWorld(v)));
|
|
}
|
|
}
|
|
|
|
|
|
public enum SubSpawnPosition
|
|
{
|
|
Path,
|
|
PathWall,
|
|
Abyss,
|
|
AbyssIsland
|
|
}
|
|
}
|
|
}
|