Files
EscapistsReintroduced/addons/ai/Scripts/Game/ERR/ERR_ConvoyPlacement.c
T

90 lines
3.9 KiB
C

// Conservative native volume check; no scenery removal or alternate placement.
class ERR_ConvoyPlacement
{
protected bool m_Obstructed;
protected int m_Visited;
protected string m_LastReason;
protected float m_LastLowest;
protected float m_LastHighest;
protected IEntity m_FirstObstruction;
protected float m_LastTrace = -1;
float LastTraceFraction() { return m_LastTrace; }
string Diagnostic()
{
string prefab = "none";
vector position;
if (m_FirstObstruction)
{
prefab = "no prefab";
if (m_FirstObstruction.GetPrefabData()) prefab = m_FirstObstruction.GetPrefabData().GetPrefabName();
position = m_FirstObstruction.GetOrigin();
}
return string.Format("%1; terrainY=%2..%3 span=%4; visited=%5; first=%6 at %7; trace=%8",
m_LastReason, m_LastLowest, m_LastHighest, m_LastHighest - m_LastLowest, m_Visited, prefab, position, m_LastTrace);
}
protected bool Occupancy(IEntity entity)
{
m_Visited++;
if (m_Visited > 128) { m_Obstructed = true; m_LastReason = "native query visit cap"; }
else if (entity && entity.GetVObject())
{
m_Obstructed = true; m_LastReason = "native VObject volume overlap"; m_FirstObstruction = entity;
}
return !m_Obstructed;
}
static bool Terrain(float origin, float lowest, float highest)
{
return ERR_ForceRules.Finite(origin) && ERR_ForceRules.Finite(lowest) && ERR_ForceRules.Finite(highest)
&& lowest > 0.3 && highest >= lowest && highest - lowest <= 0.75 && origin >= lowest - 0.3 && origin <= highest + 0.3;
}
bool Clear(vector position, float heading)
{
m_LastReason = "invalid origin or heading"; m_LastLowest = 1e20; m_LastHighest = -1e20;
m_FirstObstruction = null; m_Visited = 0; m_LastTrace = -1;
if (!ERR_ForceRules.FiniteVector(position) || !ERR_ForceRules.Finite(heading)) return false;
float radians = heading * Math.DEG2RAD;
vector forward = Vector(Math.Sin(radians), 0, Math.Cos(radians));
vector right = Vector(forward[2], 0, -forward[0]);
float lowest = 1e20, highest = -1e20;
vector low = position, high = position;
for (int x = -1; x <= 1; x++)
{
for (int z = -1; z <= 1; z++)
{
vector sample = ERR_Actors.Ground(position + right * (x * 2.0) + forward * (z * 4.5));
if (!ERR_ForceRules.FiniteVector(sample)) { m_LastReason = "nonfinite terrain sample"; return false; }
if (ChimeraWorldUtils.TryGetWaterSurfaceSimple(GetGame().GetWorld(), sample))
{ m_LastReason = "water at footprint sample"; return false; }
lowest = Math.Min(lowest, sample[1]); highest = Math.Max(highest, sample[1]);
low[0] = Math.Min(low[0], sample[0]); low[2] = Math.Min(low[2], sample[2]);
high[0] = Math.Max(high[0], sample[0]); high[2] = Math.Max(high[2], sample[2]);
}
}
m_LastLowest = lowest; m_LastHighest = highest;
if (!Terrain(position[1], lowest, highest))
{ m_LastReason = "terrain origin/height/span check"; return false; }
low[1] = lowest + 0.05; high[1] = highest + 3.5;
m_Obstructed = false; m_Visited = 0;
GetGame().GetWorld().QueryEntitiesByAABB(low, high, Occupancy);
// Entity bounding boxes can include distant branches or overhead wires.
// Their overlap is diagnostic only; the filled native volume decides collision.
vector center = (low + high) * 0.5;
TraceBox volume = new TraceBox();
volume.Start = center; volume.End = center + "0 0.01 0";
volume.Mins = low - center; volume.Maxs = high - center;
// Nine terrain/water samples already qualify the supporting ground below us.
volume.Flags = TraceFlags.ENTS | TraceFlags.OCEAN;
m_LastTrace = GetGame().GetWorld().TraceMove(volume, null);
m_Obstructed = !ERR_ForceRules.Finite(m_LastTrace) || m_LastTrace < 1;
m_FirstObstruction = null;
if (m_Obstructed)
{
m_FirstObstruction = volume.TraceEnt;
m_LastReason = "native filled volume collision";
if (!ERR_ForceRules.Finite(m_LastTrace)) m_LastReason = "invalid native trace fraction";
}
else m_LastReason = "clear";
return !m_Obstructed;
}
}