634 lines
27 KiBLFS
C#
Executable File
634 lines
27 KiBLFS
C#
Executable File
using Barotrauma;
|
|
using Barotrauma.Extensions;
|
|
using Barotrauma.Items.Components;
|
|
using FarseerPhysics.Dynamics;
|
|
using FarseerPhysics;
|
|
using HarmonyLib;
|
|
using Microsoft.Xna.Framework;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Reflection;
|
|
using System.Reflection.Emit;
|
|
using Voronoi2;
|
|
using static Barotrauma.Level;
|
|
using MoreLevelContent.Shared.Utils;
|
|
using MoreLevelContent.Shared.AI;
|
|
using Barotrauma.MoreLevelContent.Config;
|
|
|
|
namespace MoreLevelContent.Shared.Generation
|
|
{
|
|
public class CaveGenerationDirector : GenerationDirector<CaveGenerationDirector>
|
|
{
|
|
public override bool Active => true;
|
|
|
|
internal static MethodInfo level_findawayfrompoint;
|
|
internal static MethodInfo level_generatecave;
|
|
internal static MethodInfo level_calcdistfields;
|
|
internal static FieldInfo cave_genparams;
|
|
internal static FieldInfo item_statusEffectList;
|
|
internal static MethodInfo item_rotation;
|
|
internal static PropertyInfo statusEffect_offset;
|
|
internal static PropertyInfo statusEffect_characterSpawn_offset;
|
|
internal static PropertyInfo subbody_visibleBorders;
|
|
internal static PropertyInfo turret_aiCurrentTargetPriority;
|
|
|
|
internal CaveAI ActiveThalaCave;
|
|
public readonly List<CaveInitalCheckInfo> _InitialCaveCheckDebug = new();
|
|
public readonly List<EdgeValidity> _EdgeValidtity = new();
|
|
|
|
public override void Setup()
|
|
{
|
|
level_generatecave = AccessTools.Method(typeof(Level), "GenerateCave");
|
|
level_findawayfrompoint = AccessTools.Method(typeof(Level), "FindPosAwayFromMainPath");
|
|
level_calcdistfields = AccessTools.Method(typeof(Level), "CalculateTunnelDistanceField");
|
|
cave_genparams = AccessTools.Field(typeof(Cave), "CaveGenerationParams");
|
|
item_rotation = AccessTools.PropertySetter(typeof(Item), "RotationRad");
|
|
item_statusEffectList = AccessTools.Field(typeof(Item), "statusEffectLists");
|
|
statusEffect_offset = AccessTools.Property(typeof(StatusEffect), "Offset");
|
|
statusEffect_characterSpawn_offset = AccessTools.Property(typeof(StatusEffect.CharacterSpawnInfo), "Offset");
|
|
subbody_visibleBorders = AccessTools.Property(typeof(SubmarineBody), "VisibleBorders");
|
|
turret_aiCurrentTargetPriority = AccessTools.Property(typeof(Turret), nameof(Turret.AICurrentTargetPriorityMultiplier));
|
|
|
|
MethodInfo Level_Generate = AccessTools.Method(typeof(Level), "Generate", new Type[] { typeof(bool), typeof(Location), typeof(Location) });
|
|
_ = Main.Harmony.Patch(Level_Generate, transpiler: new HarmonyMethod(AccessTools.Method(typeof(CaveGenerationDirector), nameof(CaveGenerationDirector.SwapCavesTranspiler))));
|
|
|
|
MethodInfo level_update = AccessTools.Method(typeof(Level), "Update");
|
|
_ = Main.Harmony.Patch(level_update, postfix: new HarmonyMethod(AccessTools.Method(typeof(CaveGenerationDirector), nameof(CaveGenerationDirector.Update))));
|
|
|
|
MethodInfo level_remove = AccessTools.Method(typeof(Level), "Remove");
|
|
_ = Main.Harmony.Patch(level_remove, postfix: new HarmonyMethod(AccessTools.Method(typeof(CaveGenerationDirector), nameof(CaveGenerationDirector.Remove))));
|
|
#if CLIENT
|
|
MethodInfo submarine_cullEntities = AccessTools.Method(typeof(Submarine), nameof(Submarine.CullEntities));
|
|
_ = Main.Harmony.Patch(submarine_cullEntities, postfix: new HarmonyMethod(AccessTools.Method(typeof(CaveGenerationDirector), nameof(CaveGenerationDirector.SubmarineCullEntities))));
|
|
#endif
|
|
}
|
|
#if CLIENT
|
|
// This could be improved with a transpiler
|
|
static void SubmarineCullEntities(Camera cam, List<MapEntity> ___visibleEntities)
|
|
{
|
|
if (Instance.ActiveThalaCave == null) return;
|
|
Rectangle camView = cam.WorldView;
|
|
int ___CullMargin = 50;
|
|
camView = new Rectangle(camView.X - ___CullMargin, camView.Y + ___CullMargin, camView.Width + ___CullMargin * 2, camView.Height + ___CullMargin * 2);
|
|
|
|
var caveRect = new Rectangle(Instance.ActiveThalaCave.Cave.Area.X, Instance.ActiveThalaCave.Cave.Area.Y + Instance.ActiveThalaCave.Cave.Area.Height, Instance.ActiveThalaCave.Cave.Area.Width, Instance.ActiveThalaCave.Cave.Area.Height);
|
|
|
|
if (Submarine.RectsOverlap(camView, caveRect))
|
|
{
|
|
foreach (var item in Instance.ActiveThalaCave.ThalamusItems)
|
|
{
|
|
if (item.IsVisible(camView))
|
|
{
|
|
___visibleEntities.Add(item);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
public const float MIN_DIST_FROM_START = Sonar.DefaultSonarRange * 2;
|
|
const int REQUIRED_EDGE_COUNT = 1;
|
|
const float MIN_DIST_BETWEEN_ORGANS = 800;
|
|
const int MAX_OFFENSE_ITEMS = 8; //8;
|
|
|
|
static void Update(float deltaTime)
|
|
{
|
|
// Don't run the ai in editors or if we're the client
|
|
if (GameMain.GameScreen.IsEditor || Main.IsClient) return;
|
|
Instance.ActiveThalaCave?.Update(deltaTime);
|
|
}
|
|
|
|
static void Remove()
|
|
{
|
|
Instance.ActiveThalaCave?.Remove();
|
|
Instance.ActiveThalaCave = null;
|
|
}
|
|
|
|
static IEnumerable<CodeInstruction> SwapCavesTranspiler(IEnumerable<CodeInstruction> instructions, ILGenerator il)
|
|
{
|
|
var code = new List<CodeInstruction>(instructions);
|
|
bool finished = false;
|
|
Log.Debug("transpiling...");
|
|
Instance._InitialCaveCheckDebug.Clear();
|
|
Instance._EdgeValidtity.Clear();
|
|
// This insertion point needs to be change to be between lines 1230 and 1232
|
|
for (int i = 0; i < code.Count; i++) // -1 since we will be checking i + 1
|
|
{
|
|
yield return code[i];
|
|
|
|
#if CLIENT
|
|
// This is very brittle and needs to be changed
|
|
if (i == 2942)
|
|
{
|
|
Log.Debug($"Found insertion point at {i}!");
|
|
// endfinally
|
|
i++;
|
|
yield return code[i]; // ldc.i4.0
|
|
i++;
|
|
yield return code[i]; // stloc.s
|
|
i++;
|
|
yield return code[i]; //br
|
|
yield return new CodeInstruction(OpCodes.Call, AccessTools.Method(typeof(CaveGenerationDirector), nameof(CaveGenerationDirector.TrySpawnThalaCave))); // index of cell around curIndex
|
|
}
|
|
#else
|
|
if (!finished && code[i + 1].opcode == OpCodes.Ldc_I4_S && (sbyte)code[i + 1].operand == 13)
|
|
{
|
|
Log.Debug($"Found insertion point at {i}!");
|
|
yield return new CodeInstruction(OpCodes.Call, AccessTools.Method(typeof(CaveGenerationDirector), nameof(CaveGenerationDirector.TrySpawnThalaCave))); // index of cell around curIndex
|
|
finished = true;
|
|
}
|
|
#endif
|
|
}
|
|
}
|
|
|
|
static void TrySpawnThalaCave()
|
|
{
|
|
// TODO: Thalamus Caves currently cause crashes in multiplayer, fix this
|
|
if (!GameMain.IsSingleplayer) return;
|
|
|
|
if (!ConfigManager.Instance.Config.NetworkedConfig.GeneralConfig.EnableThalamusCaves || Loaded.GenerationParams.ThalamusProbability == 0 || Instance.ActiveThalaCave != null) return;
|
|
var caveParams = CaveGenerationParams.CaveParams.Where(c =>
|
|
{
|
|
Log.Debug(c.Identifier.ToString());
|
|
return c.Identifier == "thalamuscave";
|
|
}).FirstOrDefault();
|
|
|
|
if (caveParams == null)
|
|
{
|
|
Log.Error("Unable to find thalacave perfab!");
|
|
return;
|
|
}
|
|
|
|
foreach (var cave in Loaded.Caves)
|
|
{
|
|
if (Vector2.DistanceSquared(cave.StartPos.ToVector2(), Loaded.StartPosition) <= MIN_DIST_FROM_START * MIN_DIST_FROM_START)
|
|
{
|
|
// Skip caves too close to the start of the level
|
|
continue;
|
|
}
|
|
|
|
// find valid caves
|
|
bool isValid = cave.Tunnels.Where(
|
|
t => {
|
|
int count = t.Cells.Where(
|
|
c =>
|
|
{
|
|
bool result = CanSeeMainPath(c, out List<GraphEdge> edges);
|
|
if (result)
|
|
{
|
|
Instance._InitialCaveCheckDebug.Add(new CaveInitalCheckInfo(c, edges));
|
|
}
|
|
return result;
|
|
}
|
|
).Count();
|
|
Log.Debug($"Valid Edges: {count} Require Edges: {REQUIRED_EDGE_COUNT}");
|
|
return count >= REQUIRED_EDGE_COUNT;
|
|
}).Any();
|
|
|
|
if (isValid)
|
|
{
|
|
Log.Debug("Valid cave found!");
|
|
if (MakeThalaCave(cave))
|
|
{
|
|
cave_genparams.SetValue(cave, caveParams);
|
|
Log.Debug("Updated generation params");
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
Log.Debug("No valid caves found");
|
|
}
|
|
|
|
static bool CanSeeMainPath(VoronoiCell cell, out List<GraphEdge> validEdges)
|
|
{
|
|
validEdges = new List<GraphEdge>();
|
|
|
|
// This is a quick test done to see if we're likely to have a direct LOS to the main path
|
|
// We don't care if these edges are solid yet because these aren't the edges we'll be using for spawning
|
|
foreach (var edge in cell.Edges.Where(e => e.NextToMainPath || e.NextToSidePath))
|
|
{
|
|
validEdges.Add(edge);
|
|
}
|
|
|
|
return validEdges.Any();
|
|
}
|
|
|
|
private static bool IsThalamus(MapEntityPrefab entityPrefab) => entityPrefab.HasSubCategory("thalamus");
|
|
private static Vector2 ClosestPathPoint(Cave cave)
|
|
{
|
|
var pathPoints = Loaded.PositionsOfInterest.Where(poi => poi.PositionType == PositionType.MainPath || poi.PositionType == PositionType.SidePath).ToList();
|
|
|
|
Vector2 closestPos = Vector2.Zero;
|
|
float dist = float.PositiveInfinity;
|
|
foreach (var point in pathPoints)
|
|
{
|
|
float newDist = Vector2.DistanceSquared(point.Position.ToVector2(), cave.StartPos.ToVector2());
|
|
if (newDist < dist)
|
|
{
|
|
closestPos = point.Position.ToVector2();
|
|
dist = newDist;
|
|
}
|
|
}
|
|
return closestPos;
|
|
}
|
|
|
|
private readonly List<(Vector2, Vector2)> wallDebug = new List<(Vector2, Vector2)>();
|
|
|
|
static bool MakeThalaCave(Cave cave)
|
|
{
|
|
// Roll
|
|
var lvlRand = MLCUtils.GetLevelRandom();
|
|
// 65% chance to check if the level can have a cave, should make it decently rare
|
|
if (lvlRand.NextDouble() > 0.65 && Main.IsRelase) return false;
|
|
|
|
// PoCM3hEa <- seed
|
|
|
|
List<VoronoiCell> caveWallCells = GetCaveWallCells(cave);
|
|
Log.Debug($"Wall Cells: {caveWallCells.Count}");
|
|
|
|
// Spawn thalamus items
|
|
List<Item> thalamusItems = new List<Item>();
|
|
var thalamusPrefabs = ItemPrefab.Prefabs.Where(p => IsThalamus(p));
|
|
var gunPrefab = thalamusPrefabs.Where(p => p.Tags.Contains("fleshgun_cave") && p.Tags.Contains("turret")).FirstOrDefault();
|
|
var largeSpikePrefab = thalamusPrefabs.Where(p => p.Tags.Contains("fleshspike_cave")).FirstOrDefault();
|
|
var smallSpikePrefab = thalamusPrefabs.Where(p => p.Tags.Contains("fleshspikesmall_cave")).FirstOrDefault();
|
|
var spawnerPrefab = thalamusPrefabs.Where(p => p.Tags.Contains("cellspawnorgan_cave")).FirstOrDefault();
|
|
var ammosackPrefab = thalamusPrefabs.Where(p => p.Tags.Contains("fleshgunequipment_cave")).FirstOrDefault();
|
|
var storageOrgan = thalamusPrefabs.Where(p => p.Tags.Contains("storageorgan_cave")).FirstOrDefault();
|
|
var acidVent = thalamusPrefabs.Where(p => p.Tags.Contains("stomachacidvent")).FirstOrDefault();
|
|
|
|
var pathPoint = ClosestPathPoint(cave);
|
|
List<GraphEdge> entranceEdges = GetEdgesFacingPoint();
|
|
|
|
// Put some debugging test criteria here to see why the walls are failing the test
|
|
var insideEdges = caveWallCells.SelectMany(c =>
|
|
c.Edges.Where((e) =>
|
|
{
|
|
EdgeValidity validity = new EdgeValidity(e, pathPoint);
|
|
Instance._EdgeValidtity.Add(validity);
|
|
return validity.IsValidEdge;
|
|
})).ToList();
|
|
|
|
if (insideEdges.Count == 0)
|
|
{
|
|
Log.Warn("Failed to find any inside edges, spawn aborted.");
|
|
return false;
|
|
}
|
|
|
|
GraphEdge brainEdge = null;
|
|
float closestDist = float.PositiveInfinity;
|
|
float curDist;
|
|
foreach (var edge in insideEdges)
|
|
{
|
|
curDist = Vector2.DistanceSquared(edge.Center, cave.EndPos.ToVector2());
|
|
if (curDist < closestDist)
|
|
{
|
|
brainEdge = edge;
|
|
closestDist = curDist;
|
|
}
|
|
}
|
|
// Prevent other organs from spawning inside the brain
|
|
_ = insideEdges.Remove(brainEdge);
|
|
List<Item> fleshGuns = new List<Item>();
|
|
|
|
CreateOffensiveItems();
|
|
CreateDefensiveItems();
|
|
|
|
Instance.ActiveThalaCave = new CaveAI(thalamusItems, brainEdge, cave);
|
|
|
|
_ = Loaded.PositionsOfInterest.RemoveAll(poi => poi.Cave == cave);
|
|
|
|
return true;
|
|
|
|
|
|
// Methods
|
|
|
|
void CreateOffensiveItems()
|
|
{
|
|
Queue<Action> offensiveItems = new Queue<Action>();
|
|
// Limit offensive items to a max of 8
|
|
for (int i = 0; i < Math.Min(entranceEdges.Count, MAX_OFFENSE_ITEMS); i++)
|
|
{
|
|
// Always spawn a flesh gun first
|
|
if (i % 2 == 0)
|
|
{
|
|
offensiveItems.Enqueue(SpawnFleshGun);
|
|
}
|
|
else
|
|
{
|
|
offensiveItems.Enqueue(SpawnFleshSpike);
|
|
}
|
|
}
|
|
while (offensiveItems.Count > 0)
|
|
{
|
|
offensiveItems.Dequeue().Invoke();
|
|
}
|
|
}
|
|
|
|
void CreateDefensiveItems()
|
|
{
|
|
int totalSpawnLocations = insideEdges.Count;
|
|
int cellSpawns = totalSpawnLocations / 4;
|
|
// Spawn fleshgun ammo sacks before we spawn any cell spawners
|
|
// since they're required for the fleshguns to work
|
|
foreach (var fleshgun in fleshGuns)
|
|
{
|
|
var ammosack = SpawnOrgan(ammosackPrefab, GetEdge(insideEdges, true));
|
|
fleshgun.AddLinked(ammosack);
|
|
}
|
|
|
|
for (int i = 0; i < cellSpawns; i++)
|
|
{
|
|
if (insideEdges.Count != 0) break;
|
|
if (i % 2 == 0)
|
|
{
|
|
SpawnCellSpawner(GetEdge(insideEdges, true));
|
|
}
|
|
else
|
|
{
|
|
if (i % 3 == 0)
|
|
{
|
|
SpawnSmallFleshSpike();
|
|
} else
|
|
{
|
|
SpawnAcidVent();
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
// Ensure there is always 4 organs
|
|
int organCount = Math.Max(insideEdges.Count / 8, 4);
|
|
|
|
// Don't let the organ count go over the remaining valid edges
|
|
organCount = Math.Min(insideEdges.Count, organCount);
|
|
|
|
for (int i = 0; i < organCount; i++)
|
|
{
|
|
if (insideEdges.Count == 0) break;
|
|
_ = SpawnOrgan(storageOrgan, GetEdge(insideEdges, true));
|
|
}
|
|
}
|
|
|
|
void SpawnFleshGun()
|
|
{
|
|
Item fleshgun = new Item(gunPrefab, Vector2.Zero, null);
|
|
thalamusItems.Add(fleshgun);
|
|
fleshGuns.Add(fleshgun);
|
|
GraphEdge edge = GetEdge(entranceEdges);
|
|
if (edge == null) return;
|
|
int radius = fleshgun.StaticBodyConfig.GetAttributeInt("radius", 0);
|
|
Vector2 dir = MLCUtils.PositionItemOnEdge(fleshgun, edge, radius);
|
|
float angle = Angle(dir);
|
|
Turret turret = fleshgun.GetComponent<Turret>();
|
|
turret.RotationLimits = new Vector2(-angle - 90, -angle + 90);
|
|
turret.AIRange = (float)(Sonar.DefaultSonarRange * 0.8);
|
|
turret.Reload = 10f;
|
|
|
|
Log.Debug($"Placed fleshgun at {fleshgun.Position}");
|
|
}
|
|
|
|
void SpawnSmallFleshSpike()
|
|
{
|
|
Item spike = new Item(smallSpikePrefab, Vector2.Zero, null);
|
|
GraphEdge edge = GetEdge(insideEdges);
|
|
Turret turret = ConfigureTurret(spike, edge);
|
|
if (turret == null) return;
|
|
turret.TargetCharacters = true;
|
|
turret.TargetHumans = true;
|
|
turret.TargetItems = false;
|
|
Log.Debug($"Placed small spike at {spike.Position}");
|
|
}
|
|
|
|
void SpawnAcidVent()
|
|
{
|
|
Item vent = new Item(acidVent, Vector2.Zero, null);
|
|
GraphEdge edge = GetEdge(insideEdges);
|
|
Turret turret = ConfigureTurret(vent, edge, 35);
|
|
if (turret == null) return;
|
|
turret.TargetCharacters = true;
|
|
turret.TargetHumans = true;
|
|
turret.TargetItems = false;
|
|
Log.Debug($"Placed acid vent at {vent.Position}");
|
|
}
|
|
|
|
void SpawnFleshSpike()
|
|
{
|
|
Item spike = new Item(largeSpikePrefab, Vector2.Zero, null);
|
|
GraphEdge edge = GetEdge(entranceEdges);
|
|
Turret turret = ConfigureTurret(spike, edge);
|
|
if (turret == null) return;
|
|
turret.TargetItems = true;
|
|
turret.TargetSubmarines = true;
|
|
turret.TargetCharacters = false;
|
|
Log.Debug($"Placed spike at {spike.Position}");
|
|
}
|
|
|
|
Turret ConfigureTurret(Item spike, GraphEdge edge, float angleRange = 1f)
|
|
{
|
|
thalamusItems.Add(spike);
|
|
if (edge == null) return null;
|
|
int height = spike.StaticBodyConfig.GetAttributeInt("height", 0);
|
|
Vector2 dir = MLCUtils.PositionItemOnEdge(spike, edge, height);
|
|
float angle = Angle(dir);
|
|
spike.SpriteDepth = 1;
|
|
Turret turret = spike.GetComponent<Turret>();
|
|
turret.RotationLimits = new Vector2(-angle - angleRange, -angle + angleRange);
|
|
turret.RandomMovement = false;
|
|
turret.AimDelay = false;
|
|
// special sauce?
|
|
// turret_aiCurrentTargetPriority.SetValue(turret, 0.1f);
|
|
|
|
// config status effects
|
|
Dictionary<ActionType, List<StatusEffect>> dic = (Dictionary<ActionType, List<StatusEffect>>)item_statusEffectList.GetValue(spike);
|
|
if (dic?.TryGetValue(ActionType.OnUse, out List<StatusEffect> effects) ?? false)
|
|
{
|
|
// Adjust offsets of on use status effects to match our angle
|
|
foreach (var effect in effects)
|
|
{
|
|
float dist = effect.Offset.Y;
|
|
float turretRot = angle;
|
|
float turretRotRad = MathHelper.ToRadians(turretRot);
|
|
Vector2 newOffset = new Vector2((float)Math.Cos(turretRotRad), (float)Math.Sin(turretRotRad)) * dist;
|
|
statusEffect_offset.SetValue(effect, newOffset);
|
|
foreach (var spawnEffect in effect.SpawnCharacters)
|
|
{
|
|
dist = spawnEffect.Offset.Y;
|
|
newOffset = new Vector2((float)Math.Cos(turretRotRad), (float)Math.Sin(turretRotRad)) * dist;
|
|
statusEffect_characterSpawn_offset.SetValue(spawnEffect, newOffset);
|
|
}
|
|
}
|
|
}
|
|
return turret;
|
|
}
|
|
|
|
void SpawnCellSpawner(GraphEdge edge)
|
|
{
|
|
Item spawner = new Item(spawnerPrefab, Vector2.Zero, null);
|
|
thalamusItems.Add(spawner);
|
|
Vector2 dir = MLCUtils.PositionItemOnEdge(spawner, edge, 80, true);
|
|
}
|
|
|
|
Item SpawnOrgan(ItemPrefab organPrefab, GraphEdge edge)
|
|
{
|
|
Item organ = new Item(organPrefab, Vector2.Zero, null);
|
|
thalamusItems.Add(organ);
|
|
Vector2 dir = MLCUtils.PositionItemOnEdge(organ, edge, 60, true);
|
|
return organ;
|
|
}
|
|
|
|
GraphEdge GetEdge(List<GraphEdge> edges, bool removeClose = false)
|
|
{
|
|
if (!edges.Any()) return null;
|
|
GraphEdge edge = edges.GetRandom(Rand.RandSync.ServerAndClient);
|
|
_ = edges.Remove(edge);
|
|
|
|
// Remove all valid edges that are too close to this edge
|
|
if (removeClose) _ = edges.RemoveAll(e => Vector2.DistanceSquared(edge.Center, e.Center) < MIN_DIST_BETWEEN_ORGANS * MIN_DIST_BETWEEN_ORGANS);
|
|
return edge;
|
|
}
|
|
|
|
float Angle(Vector2 dir) => (float)(MathUtils.VectorToAngle(dir) * 180 / Math.PI);
|
|
|
|
List<GraphEdge> GetEdgesFacingPoint()
|
|
{
|
|
List<GraphEdge> edges = new List<GraphEdge>();
|
|
|
|
caveWallCells
|
|
.ForEach(c =>
|
|
{
|
|
edges.AddRange(c.Edges.Where(e =>
|
|
e.IsSolid &&
|
|
WideEnough(e) &&
|
|
FacingPathPoint(e) &&
|
|
CanEdgeSeePathPoint(e)
|
|
).ToList());
|
|
});
|
|
return edges;
|
|
}
|
|
|
|
bool FacingPathPoint(GraphEdge e) => Vector2.Dot(Vector2.Normalize(e.GetNormal(null)), Vector2.Normalize(e.Center - pathPoint)) >= 0;
|
|
bool WideEnough(GraphEdge e, float size = 200) => Vector2.DistanceSquared(e.Point1, e.Point2) > size * size;
|
|
bool CanEdgeSeePathPoint(GraphEdge e)
|
|
{
|
|
return !PhysUtil.RaycastWorld(e.SimPosition(), ConvertUnits.ToSimUnits(pathPoint), new List<Body> { }).Hit;
|
|
}
|
|
bool CanPosSeePathPoint(Vector2 simPos) => !PhysUtil.RaycastWorld(simPos, ConvertUnits.ToSimUnits(pathPoint), new List<Body> { }).Hit;
|
|
bool InsideExtraWall(GraphEdge e)
|
|
{
|
|
// this doesn't work at all
|
|
// SAD
|
|
bool cell1 = false;
|
|
bool cell2 = false;
|
|
if (e.Cell1 != null)
|
|
{
|
|
cell1 = Loaded.ExtraWalls.Any(w => w.IsPointInside(e.Cell1.Center));
|
|
}
|
|
if (e.Cell2 != null)
|
|
{
|
|
cell2 = Loaded.ExtraWalls.Any(w => w.IsPointInside(e.Cell2.Center));
|
|
}
|
|
return cell1 || cell2;
|
|
}
|
|
|
|
Vector2 GetEdgeDir(GraphEdge edge) => edge.GetNormal(null);
|
|
}
|
|
|
|
static List<VoronoiCell> GetCaveWallCells(Cave cave)
|
|
{
|
|
List<VoronoiCell> caveWalls = new List<VoronoiCell>();
|
|
|
|
foreach (var caveCell in cave.Tunnels.SelectMany(t => t.Cells))
|
|
{
|
|
foreach (var edge in caveCell.Edges)
|
|
{
|
|
if (!edge.NextToCave) { continue; }
|
|
if (edge.Cell1?.CellType == CellType.Solid && !caveWalls.Contains(edge.Cell1))
|
|
{
|
|
caveWalls.Add(edge.Cell1);
|
|
}
|
|
if (edge.Cell2?.CellType == CellType.Solid && !caveWalls.Contains(edge.Cell2))
|
|
{
|
|
caveWalls.Add(edge.Cell2);
|
|
}
|
|
}
|
|
}
|
|
return caveWalls;
|
|
}
|
|
}
|
|
|
|
public struct CaveInitalCheckInfo
|
|
{
|
|
public CaveInitalCheckInfo(VoronoiCell cell, List<GraphEdge> validEdges)
|
|
{
|
|
Cell = cell;
|
|
ValidEdges = validEdges;
|
|
}
|
|
public List<GraphEdge> ValidEdges;
|
|
public VoronoiCell Cell;
|
|
public Vector2 GetEdgeDrawPosition(GraphEdge edge)
|
|
{
|
|
return new Vector2(edge.Center.X, -edge.Center.Y);
|
|
}
|
|
}
|
|
|
|
public struct EdgeValidity
|
|
{
|
|
//e.IsSolid &&
|
|
// !CanEdgeSeePathPoint(e) &&
|
|
// WideEnough(e) &&
|
|
// !InsideExtraWall(e)
|
|
public EdgeValidity(GraphEdge e, Vector2 pathPoint)
|
|
{
|
|
IsValidEdge = false;
|
|
FailReason = "Valid";
|
|
Hit = default;
|
|
Position = new Vector2(e.Center.X, -e.Center.Y);
|
|
|
|
if (!e.IsSolid)
|
|
{
|
|
FailReason = "Not solid";
|
|
return;
|
|
}
|
|
|
|
if (CanEdgeSeePoint(e, pathPoint, out RayHit hit))
|
|
{
|
|
FailReason = "Not Inside";
|
|
Hit = hit;
|
|
return;
|
|
}
|
|
|
|
if (!WideEnough(e))
|
|
{
|
|
FailReason = "Too Small";
|
|
return;
|
|
}
|
|
|
|
IsValidEdge = true;
|
|
}
|
|
|
|
public static bool CanEdgeSeePoint(GraphEdge e, Vector2 point, out RayHit hit)
|
|
{
|
|
hit = PhysUtil.RaycastWorld(e.SimPosition(), ConvertUnits.ToSimUnits(point), new List<Body> { });
|
|
return !hit.Hit;
|
|
}
|
|
|
|
public static bool WideEnough(GraphEdge e, float size = 200) => Vector2.DistanceSquared(e.Point1, e.Point2) > size * size;
|
|
|
|
public RayHit Hit;
|
|
|
|
public string FailReason;
|
|
|
|
public bool IsValidEdge;
|
|
|
|
public Vector2 Position;
|
|
}
|
|
|
|
|
|
public static class GraphEdgeExtensions
|
|
{
|
|
public static Vector2 SimPosition(this GraphEdge edge) => ConvertUnits.ToSimUnits(edge.Center);
|
|
}
|
|
}
|