Track LocalMods as part of monolith
This commit is contained in:
+631
@@ -0,0 +1,631 @@
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user